mirror of
https://github.com/SideStore/SideStore.git
synced 2026-04-09 04:05:39 +02:00
Compare commits
102 Commits
0.5.2
...
junepark67
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d49098a8be | ||
|
|
b4e18c50d3 | ||
|
|
d18482a04a | ||
|
|
f3d9dd777d | ||
|
|
e117c4b9a3 | ||
|
|
95666178e5 | ||
|
|
56403466b9 | ||
|
|
c7344ef548 | ||
|
|
71c4abfce8 | ||
|
|
0613af2240 | ||
|
|
dd832ad6df | ||
|
|
8cf7bc9998 | ||
|
|
c1cf11c04c | ||
|
|
0058c40f46 | ||
|
|
1397389f95 | ||
|
|
6a2d3e1d22 | ||
|
|
46ac704013 | ||
|
|
0fdab2a5c5 | ||
|
|
8f4586bfef | ||
|
|
52d0c9861f | ||
|
|
feace61eb4 | ||
|
|
1f5cc8f283 | ||
|
|
60b8520237 | ||
|
|
6a67c5e9a2 | ||
|
|
bcc241518c | ||
|
|
6dfa8f1556 | ||
|
|
19dde692b2 | ||
|
|
14dc93b5d2 | ||
|
|
547620235e | ||
|
|
b43fd0a54b | ||
|
|
8b782c9416 | ||
|
|
aab4e62e24 | ||
|
|
1713fccfc4 | ||
|
|
83ece72ae1 | ||
|
|
d60bcc49e1 | ||
|
|
bc9c37adda | ||
|
|
2583c7f617 | ||
|
|
fea5229e02 | ||
|
|
68be615057 | ||
|
|
370cafcba0 | ||
|
|
f923c1602e | ||
|
|
50a85be872 | ||
|
|
aae4725a3c | ||
|
|
9d76ee9f19 | ||
|
|
34a101b796 | ||
|
|
49b1fd751c | ||
|
|
4c5bf7bb7d | ||
|
|
2d71631d93 | ||
|
|
fa0d933956 | ||
|
|
b5d6384a07 | ||
|
|
d39644a4c9 | ||
|
|
a2feb34dc1 | ||
|
|
7e5fe64153 | ||
|
|
44175d071c | ||
|
|
bae26de444 | ||
|
|
b78707808d | ||
|
|
d41518581a | ||
|
|
4abbfe6142 | ||
|
|
dae813d80c | ||
|
|
af89b178ad | ||
|
|
8c269207fd | ||
|
|
42ecd38517 | ||
|
|
9f7d4dee49 | ||
|
|
458b8e491e | ||
|
|
495e621e69 | ||
|
|
c986512b5f | ||
|
|
d277754ae5 | ||
|
|
2ef2e2f26b | ||
|
|
23a53034fa | ||
|
|
ce57d72a78 | ||
|
|
502b89d890 | ||
|
|
5f0015fad0 | ||
|
|
c81236957b | ||
|
|
970ab38b27 | ||
|
|
8a5c31b81d | ||
|
|
8508fe79b5 | ||
|
|
3859e98801 | ||
|
|
a759c7be9e | ||
|
|
12fc6cf6e2 | ||
|
|
580db6530e | ||
|
|
9c67c237ee | ||
|
|
357d85a72e | ||
|
|
88ad828ce0 | ||
|
|
a95625a34a | ||
|
|
95e00d81f5 | ||
|
|
c2e386a5c5 | ||
|
|
a76aade4ff | ||
|
|
65c9986103 | ||
|
|
9e2b9b6639 | ||
|
|
cf373634d7 | ||
|
|
b3d5d976b4 | ||
|
|
c3c31995ce | ||
|
|
7e92e17429 | ||
|
|
88ab8fa8d7 | ||
|
|
ebe78932bf | ||
|
|
2e613e6d15 | ||
|
|
35ee92db12 | ||
|
|
04d9f760ad | ||
|
|
4f52743be8 | ||
|
|
32cae7a5b2 | ||
|
|
c2c0e3b790 | ||
|
|
6d36a30787 |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1 +1 @@
|
|||||||
* @JoeMatt @lonkelle
|
* @JoeMatt @lonkelle @nythepegasus @Spidy123222 @SternXD
|
||||||
|
|||||||
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -2,8 +2,7 @@ name: Bug Report
|
|||||||
description: Report a bug
|
description: Report a bug
|
||||||
title: "[BUG] "
|
title: "[BUG] "
|
||||||
labels: ["bug"]
|
labels: ["bug"]
|
||||||
assignees:
|
assignees: []
|
||||||
- naturecodevoid
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
3
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
3
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -2,8 +2,7 @@ name: Feature Request
|
|||||||
description: Suggest a feature
|
description: Suggest a feature
|
||||||
title: "[FEATURE REQUEST] "
|
title: "[FEATURE REQUEST] "
|
||||||
labels: ["enhancement"]
|
labels: ["enhancement"]
|
||||||
assignees:
|
assignees: []
|
||||||
- naturecodevoid
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
3
.github/pull_request_template.md
vendored
3
.github/pull_request_template.md
vendored
@@ -10,6 +10,3 @@
|
|||||||
<!-- Example: -->
|
<!-- Example: -->
|
||||||
- [x] Finish UI changes
|
- [x] Finish UI changes
|
||||||
- [ ] Test
|
- [ ] Test
|
||||||
|
|
||||||
<!-- If your PR doesn't close an issue, you can remove the next line. -->
|
|
||||||
Closes #1234
|
|
||||||
|
|||||||
55
.github/workflows/attach_build_products.yml
vendored
55
.github/workflows/attach_build_products.yml
vendored
@@ -20,3 +20,58 @@ jobs:
|
|||||||
format: name
|
format: name
|
||||||
addTo: pull
|
addTo: pull
|
||||||
# addTo: pullandissues
|
# addTo: pullandissues
|
||||||
|
nightly-link-comment:
|
||||||
|
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
# This snippet is public-domain, taken from
|
||||||
|
# https://github.com/oprypin/nightly.link/blob/master/.github/workflows/pr-comment.yml
|
||||||
|
script: |
|
||||||
|
async function upsertComment(owner, repo, issue_number, purpose, body) {
|
||||||
|
const {data: comments} = await github.rest.issues.listComments(
|
||||||
|
{owner, repo, issue_number});
|
||||||
|
|
||||||
|
const marker = `<!-- bot: ${purpose} -->`;
|
||||||
|
body = marker + "\n" + body;
|
||||||
|
|
||||||
|
const existing = comments.filter((c) => c.body.includes(marker));
|
||||||
|
if (existing.length > 0) {
|
||||||
|
const last = existing[existing.length - 1];
|
||||||
|
core.info(`Updating comment ${last.id}`);
|
||||||
|
await github.rest.issues.updateComment({
|
||||||
|
owner, repo,
|
||||||
|
body,
|
||||||
|
comment_id: last.id,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
core.info(`Creating a comment in issue / PR #${issue_number}`);
|
||||||
|
await github.rest.issues.createComment({issue_number, body, owner, repo});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {owner, repo} = context.repo;
|
||||||
|
const run_id = ${{github.event.workflow_run.id}};
|
||||||
|
|
||||||
|
const pull_requests = ${{ toJSON(github.event.workflow_run.pull_requests) }};
|
||||||
|
if (!pull_requests.length) {
|
||||||
|
return core.error("This workflow doesn't match any pull requests!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const artifacts = await github.paginate(
|
||||||
|
github.rest.actions.listWorkflowRunArtifacts, {owner, repo, run_id});
|
||||||
|
if (!artifacts.length) {
|
||||||
|
return core.error(`No artifacts found`);
|
||||||
|
}
|
||||||
|
let body = `Download the artifacts for this pull request (nightly.link):\n`;
|
||||||
|
for (const art of artifacts) {
|
||||||
|
body += `\n* [${art.name}.zip](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info("Review thread message body:", body);
|
||||||
|
|
||||||
|
for (const pr of pull_requests) {
|
||||||
|
await upsertComment(owner, repo, pr.number,
|
||||||
|
"nightly-link", body);
|
||||||
|
}
|
||||||
|
|||||||
7
.github/workflows/beta.yml
vendored
7
.github/workflows/beta.yml
vendored
@@ -39,6 +39,13 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
xcode-version: ${{ matrix.version }}
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
|
|
||||||
|
- name: Cache Build
|
||||||
|
uses: irgaly/xcode-cache@v1
|
||||||
|
with:
|
||||||
|
key: xcode-cache-deriveddata-${{ github.sha }}
|
||||||
|
restore-keys: xcode-cache-deriveddata
|
||||||
|
|
||||||
- name: Build SideStore
|
- name: Build SideStore
|
||||||
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
|||||||
9
.github/workflows/nightly.yml
vendored
9
.github/workflows/nightly.yml
vendored
@@ -48,6 +48,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
xcode-version: ${{ matrix.version }}
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
|
- name: Cache Build
|
||||||
|
uses: irgaly/xcode-cache@v1
|
||||||
|
with:
|
||||||
|
key: xcode-cache-deriveddata-${{ github.sha }}
|
||||||
|
restore-keys: xcode-cache-deriveddata-
|
||||||
|
|
||||||
- name: Build SideStore
|
- name: Build SideStore
|
||||||
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||||
|
|
||||||
@@ -101,6 +107,3 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
||||||
path: ./*.dSYM/
|
path: ./*.dSYM/
|
||||||
|
|
||||||
- name: Reset cache for apps.sidestore.io/nightly
|
|
||||||
run: sleep 10 && curl https://apps.sidestore.io/reset-cache/nightly/${{ secrets.SIDESOURCE_KEY }}
|
|
||||||
|
|||||||
6
.github/workflows/pr.yml
vendored
6
.github/workflows/pr.yml
vendored
@@ -39,6 +39,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
xcode-version: ${{ matrix.version }}
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
|
- name: Cache Build
|
||||||
|
uses: irgaly/xcode-cache@v1
|
||||||
|
with:
|
||||||
|
key: xcode-cache-deriveddata-${{ github.sha }}
|
||||||
|
restore-keys: xcode-cache-deriveddata-
|
||||||
|
|
||||||
- name: Build SideStore
|
- name: Build SideStore
|
||||||
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/stable.yml
vendored
6
.github/workflows/stable.yml
vendored
@@ -39,6 +39,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
xcode-version: ${{ matrix.version }}
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
|
- name: Cache Build
|
||||||
|
uses: irgaly/xcode-cache@v1
|
||||||
|
with:
|
||||||
|
key: xcode-cache-deriveddata-${{ github.sha }}
|
||||||
|
restore-keys: xcode-cache-deriveddata-
|
||||||
|
|
||||||
- name: Build SideStore
|
- name: Build SideStore
|
||||||
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
run: make build | xcpretty && exit ${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -19,7 +19,7 @@ archive.xcarchive
|
|||||||
*.perspectivev3
|
*.perspectivev3
|
||||||
!default.perspectivev3
|
!default.perspectivev3
|
||||||
xcuserdata
|
xcuserdata
|
||||||
|
AltStore.xcodeproj/project.xcworkspace/xcshareddata/swiftpm
|
||||||
## Other
|
## Other
|
||||||
*.xccheckout
|
*.xccheckout
|
||||||
*.moved-aside
|
*.moved-aside
|
||||||
|
|||||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -9,7 +9,7 @@
|
|||||||
url = https://github.com/libimobiledevice/libusbmuxd.git
|
url = https://github.com/libimobiledevice/libusbmuxd.git
|
||||||
[submodule "Dependencies/libplist"]
|
[submodule "Dependencies/libplist"]
|
||||||
path = Dependencies/libplist
|
path = Dependencies/libplist
|
||||||
url = https://github.com/libimobiledevice/libplist.git
|
url = https://github.com/SideStore/libplist.git
|
||||||
[submodule "Dependencies/MarkdownAttributedString"]
|
[submodule "Dependencies/MarkdownAttributedString"]
|
||||||
path = Dependencies/MarkdownAttributedString
|
path = Dependencies/MarkdownAttributedString
|
||||||
url = https://github.com/chockenberry/MarkdownAttributedString.git
|
url = https://github.com/chockenberry/MarkdownAttributedString.git
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
03F06CD52942C27E001C4D68 /* Bundle+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */; };
|
03F06CD52942C27E001C4D68 /* Bundle+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314122A05D4C00370A3C /* Bundle+AltStore.swift */; };
|
||||||
|
0E05025A2BEC83C500879B5C /* OperatingSystemVersion+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0502592BEC83C500879B5C /* OperatingSystemVersion+Comparable.swift */; };
|
||||||
|
0E05025C2BEC947000879B5C /* String+SideStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E05025B2BEC947000879B5C /* String+SideStore.swift */; };
|
||||||
0E1A1F912AE36A9700364CAD /* bytearray.c in Sources */ = {isa = PBXBuildFile; fileRef = 0E1A1F902AE36A9600364CAD /* bytearray.c */; };
|
0E1A1F912AE36A9700364CAD /* bytearray.c in Sources */ = {isa = PBXBuildFile; fileRef = 0E1A1F902AE36A9600364CAD /* bytearray.c */; };
|
||||||
0E764E172ADFF5740043DD4E /* AltBackup.ipa in Resources */ = {isa = PBXBuildFile; fileRef = 0E764E162ADFF5740043DD4E /* AltBackup.ipa */; };
|
0E764E172ADFF5740043DD4E /* AltBackup.ipa in Resources */ = {isa = PBXBuildFile; fileRef = 0E764E162ADFF5740043DD4E /* AltBackup.ipa */; };
|
||||||
0EA1665B2ADFE0D2003015C1 /* out-limd.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166472ADFE0D1003015C1 /* out-limd.c */; };
|
0EA1665B2ADFE0D2003015C1 /* out-limd.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166472ADFE0D1003015C1 /* out-limd.c */; };
|
||||||
@@ -35,13 +37,20 @@
|
|||||||
0EA1667B2ADFE140003015C1 /* Structure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA1665A2ADFE0D2003015C1 /* Structure.cpp */; };
|
0EA1667B2ADFE140003015C1 /* Structure.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0EA1665A2ADFE0D2003015C1 /* Structure.cpp */; };
|
||||||
0EA1667D2ADFE140003015C1 /* xplist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166592ADFE0D2003015C1 /* xplist.c */; };
|
0EA1667D2ADFE140003015C1 /* xplist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA166592ADFE0D2003015C1 /* xplist.c */; };
|
||||||
0EA1667E2ADFE140003015C1 /* time64.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA1664C2ADFE0D1003015C1 /* time64.c */; };
|
0EA1667E2ADFE140003015C1 /* time64.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA1664C2ADFE0D1003015C1 /* time64.c */; };
|
||||||
|
0EA4263A2C2230150026D7FB /* AnisetteServerList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA426392C2230150026D7FB /* AnisetteServerList.swift */; };
|
||||||
0EA4B9BC2AE4A414009209CE /* plist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA4B9BB2AE4A3F6009209CE /* plist.c */; };
|
0EA4B9BC2AE4A414009209CE /* plist.c in Sources */ = {isa = PBXBuildFile; fileRef = 0EA4B9BB2AE4A3F6009209CE /* plist.c */; };
|
||||||
|
0EE7FDC42BE8BC7900D1E390 /* ALTLocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */; };
|
||||||
|
0EE7FDC62BE8CEA300D1E390 /* ALTLocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */; };
|
||||||
|
0EE7FDC72BE8CF4100D1E390 /* ALTWrappedError.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC02BE8BC2100D1E390 /* ALTWrappedError.m */; };
|
||||||
|
0EE7FDC82BE8CF4800D1E390 /* ALTWrappedError.h in Headers */ = {isa = PBXBuildFile; fileRef = 0EE7FDC22BE8BC4200D1E390 /* ALTWrappedError.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
|
0EE7FDC92BE8D07400D1E390 /* NSError+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+AltStore.swift */; };
|
||||||
|
0EE7FDCB2BE8D12B00D1E390 /* ALTLocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */; };
|
||||||
|
0EE7FDCD2BE9124400D1E390 /* ErrorDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDCC2BE9124400D1E390 /* ErrorDetailsViewController.swift */; };
|
||||||
19104D952909BAEA00C49C7B /* libimobiledevice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF45872B2298D31600BD7491 /* libimobiledevice.a */; };
|
19104D952909BAEA00C49C7B /* libimobiledevice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF45872B2298D31600BD7491 /* libimobiledevice.a */; };
|
||||||
19104DB52909C06D00C49C7B /* EmotionalDamage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19104DB42909C06D00C49C7B /* EmotionalDamage.swift */; };
|
19104DB52909C06D00C49C7B /* EmotionalDamage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19104DB42909C06D00C49C7B /* EmotionalDamage.swift */; };
|
||||||
19104DBC2909C4E500C49C7B /* libEmotionalDamage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19104DB22909C06C00C49C7B /* libEmotionalDamage.a */; };
|
19104DBC2909C4E500C49C7B /* libEmotionalDamage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19104DB22909C06C00C49C7B /* libEmotionalDamage.a */; };
|
||||||
191E5FB4290A5DA0001A3B7C /* libminimuxer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 191E5FAB290A5D92001A3B7C /* libminimuxer.a */; };
|
191E5FB4290A5DA0001A3B7C /* libminimuxer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 191E5FAB290A5D92001A3B7C /* libminimuxer.a */; };
|
||||||
191E5FDC290AFA5C001A3B7C /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 191E5FDB290AFA5C001A3B7C /* OpenSSL */; };
|
191E5FDC290AFA5C001A3B7C /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 191E5FDB290AFA5C001A3B7C /* OpenSSL */; };
|
||||||
1920B04F2924AC8300744F60 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 1920B04E2924AC8300744F60 /* Settings.bundle */; };
|
|
||||||
19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */; };
|
19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */; };
|
||||||
4879A95F2861046500FC1BBD /* AltSign in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A95E2861046500FC1BBD /* AltSign */; };
|
4879A95F2861046500FC1BBD /* AltSign in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A95E2861046500FC1BBD /* AltSign */; };
|
||||||
4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; };
|
4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; };
|
||||||
@@ -75,6 +84,7 @@
|
|||||||
B3C395F7284F362400DA9E2F /* AppCenterAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = B3C395F6284F362400DA9E2F /* AppCenterAnalytics */; };
|
B3C395F7284F362400DA9E2F /* AppCenterAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = B3C395F6284F362400DA9E2F /* AppCenterAnalytics */; };
|
||||||
B3C395F9284F362400DA9E2F /* AppCenterCrashes in Frameworks */ = {isa = PBXBuildFile; productRef = B3C395F8284F362400DA9E2F /* AppCenterCrashes */; };
|
B3C395F9284F362400DA9E2F /* AppCenterCrashes in Frameworks */ = {isa = PBXBuildFile; productRef = B3C395F8284F362400DA9E2F /* AppCenterCrashes */; };
|
||||||
B3EE16B62925E27D00B3B1F5 /* AnisetteManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3EE16B52925E27D00B3B1F5 /* AnisetteManager.swift */; };
|
B3EE16B62925E27D00B3B1F5 /* AnisetteManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3EE16B52925E27D00B3B1F5 /* AnisetteManager.swift */; };
|
||||||
|
BD4513AB2C6FA98C0052BCC0 /* AppExtensionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4513AA2C6FA98C0052BCC0 /* AppExtensionView.swift */; };
|
||||||
BF02419622F2199300129732 /* RefreshAttemptsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02419522F2199300129732 /* RefreshAttemptsViewController.swift */; };
|
BF02419622F2199300129732 /* RefreshAttemptsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02419522F2199300129732 /* RefreshAttemptsViewController.swift */; };
|
||||||
BF08858322DE795100DE9F1E /* MyAppsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858222DE795100DE9F1E /* MyAppsViewController.swift */; };
|
BF08858322DE795100DE9F1E /* MyAppsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858222DE795100DE9F1E /* MyAppsViewController.swift */; };
|
||||||
BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858422DE7EC800DE9F1E /* UpdateCollectionViewCell.swift */; };
|
BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF08858422DE7EC800DE9F1E /* UpdateCollectionViewCell.swift */; };
|
||||||
@@ -162,7 +172,6 @@
|
|||||||
BF58048A246A28F9008AE704 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF580488246A28F9008AE704 /* LaunchScreen.storyboard */; };
|
BF58048A246A28F9008AE704 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF580488246A28F9008AE704 /* LaunchScreen.storyboard */; };
|
||||||
BF580496246A3CB5008AE704 /* UIColor+AltBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF580495246A3CB5008AE704 /* UIColor+AltBackup.swift */; };
|
BF580496246A3CB5008AE704 /* UIColor+AltBackup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF580495246A3CB5008AE704 /* UIColor+AltBackup.swift */; };
|
||||||
BF580498246A3D19008AE704 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF580497246A3D19008AE704 /* UIKit.framework */; };
|
BF580498246A3D19008AE704 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF580497246A3D19008AE704 /* UIKit.framework */; };
|
||||||
BF58049B246A432D008AE704 /* NSError+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+AltStore.swift */; };
|
|
||||||
BF663C4F2433ED8200DAA738 /* FileManager+DirectorySize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF663C4E2433ED8200DAA738 /* FileManager+DirectorySize.swift */; };
|
BF663C4F2433ED8200DAA738 /* FileManager+DirectorySize.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF663C4E2433ED8200DAA738 /* FileManager+DirectorySize.swift */; };
|
||||||
BF66EE822501AE50007EE018 /* AltStoreCore.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE802501AE50007EE018 /* AltStoreCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
BF66EE822501AE50007EE018 /* AltStoreCore.h in Headers */ = {isa = PBXBuildFile; fileRef = BF66EE802501AE50007EE018 /* AltStoreCore.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */; };
|
BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */; };
|
||||||
@@ -208,7 +217,6 @@
|
|||||||
BF66EEE92501AED0007EE018 /* JSONDecoder+Properties.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE52501AED0007EE018 /* JSONDecoder+Properties.swift */; };
|
BF66EEE92501AED0007EE018 /* JSONDecoder+Properties.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE52501AED0007EE018 /* JSONDecoder+Properties.swift */; };
|
||||||
BF66EEEA2501AED0007EE018 /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE62501AED0007EE018 /* UIColor+Hex.swift */; };
|
BF66EEEA2501AED0007EE018 /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE62501AED0007EE018 /* UIColor+Hex.swift */; };
|
||||||
BF66EEEB2501AED0007EE018 /* UIApplication+AppExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE72501AED0007EE018 /* UIApplication+AppExtension.swift */; };
|
BF66EEEB2501AED0007EE018 /* UIApplication+AppExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF66EEE72501AED0007EE018 /* UIApplication+AppExtension.swift */; };
|
||||||
BF6C336224197D700034FD24 /* NSError+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+AltStore.swift */; };
|
|
||||||
BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAA242935ED00125131 /* NSAttributedString+Markdown.m */; };
|
BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAA242935ED00125131 /* NSAttributedString+Markdown.m */; };
|
||||||
BF6C8FAE2429597900125131 /* BannerCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAD2429597900125131 /* BannerCollectionViewCell.swift */; };
|
BF6C8FAE2429597900125131 /* BannerCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAD2429597900125131 /* BannerCollectionViewCell.swift */; };
|
||||||
BF6C8FB02429599900125131 /* TextCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAF2429599900125131 /* TextCollectionReusableView.swift */; };
|
BF6C8FB02429599900125131 /* TextCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C8FAF2429599900125131 /* TextCollectionReusableView.swift */; };
|
||||||
@@ -239,7 +247,7 @@
|
|||||||
BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4A22DD137F008935CF /* NavigationBar.swift */; };
|
BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4A22DD137F008935CF /* NavigationBar.swift */; };
|
||||||
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4C22DD16DE008935CF /* PillButton.swift */; };
|
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF9ABA4C22DD16DE008935CF /* PillButton.swift */; };
|
||||||
BFA8172B23C5633D001B5953 /* FetchAnisetteDataOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA8172A23C5633D001B5953 /* FetchAnisetteDataOperation.swift */; };
|
BFA8172B23C5633D001B5953 /* FetchAnisetteDataOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA8172A23C5633D001B5953 /* FetchAnisetteDataOperation.swift */; };
|
||||||
BFAECC522501B0A400528F27 /* CodableServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableServerError.swift */; };
|
BFAECC522501B0A400528F27 /* CodableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableError.swift */; };
|
||||||
BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
|
BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
|
||||||
BFAECC542501B0A400528F27 /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; };
|
BFAECC542501B0A400528F27 /* NSError+ALTServerError.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1E314922A060F400370A3C /* NSError+ALTServerError.m */; };
|
||||||
BFAECC552501B0A400528F27 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF624858BDE00DD5981 /* Connection.swift */; };
|
BFAECC552501B0A400528F27 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF624858BDE00DD5981 /* Connection.swift */; };
|
||||||
@@ -298,7 +306,7 @@
|
|||||||
BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */; };
|
BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */; };
|
||||||
BFE6325A22A83BEB00F30809 /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFE6325922A83BEB00F30809 /* Authentication.storyboard */; };
|
BFE6325A22A83BEB00F30809 /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFE6325922A83BEB00F30809 /* Authentication.storyboard */; };
|
||||||
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */; };
|
BFE6326C22A86FF300F30809 /* AuthenticationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE6326B22A86FF300F30809 /* AuthenticationOperation.swift */; };
|
||||||
BFECAC8824FD950E0077C41F /* CodableServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableServerError.swift */; };
|
BFECAC8824FD950E0077C41F /* CodableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD44605241188C300EAB90A /* CodableError.swift */; };
|
||||||
BFECAC8924FD950E0077C41F /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF22485828200DD5981 /* ConnectionManager.swift */; };
|
BFECAC8924FD950E0077C41F /* ConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF18BFF22485828200DD5981 /* ConnectionManager.swift */; };
|
||||||
BFECAC8A24FD950E0077C41F /* ALTServerError+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */; };
|
BFECAC8A24FD950E0077C41F /* ALTServerError+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF767CB2489AB5C0097E58C /* ALTServerError+Conveniences.swift */; };
|
||||||
BFECAC8B24FD950E0077C41F /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
|
BFECAC8B24FD950E0077C41F /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1E3128229F474900370A3C /* ServerProtocol.swift */; };
|
||||||
@@ -332,6 +340,7 @@
|
|||||||
D57FE84428C7DB7100216002 /* ErrorLogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */; };
|
D57FE84428C7DB7100216002 /* ErrorLogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */; };
|
||||||
D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58916FD28C7C55C00E39C8B /* LoggedError.swift */; };
|
D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D58916FD28C7C55C00E39C8B /* LoggedError.swift */; };
|
||||||
D593F1942717749A006E82DE /* PatchAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D593F1932717749A006E82DE /* PatchAppOperation.swift */; };
|
D593F1942717749A006E82DE /* PatchAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D593F1932717749A006E82DE /* PatchAppOperation.swift */; };
|
||||||
|
D5ACE84528E3B8450021CAB9 /* ClearAppCacheOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */; };
|
||||||
D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4A280E141900469595 /* ManagedPatron.swift */; };
|
D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4A280E141900469595 /* ManagedPatron.swift */; };
|
||||||
D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4D280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel */; };
|
D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5CA0C4D280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel */; };
|
||||||
D5DAE0942804B0B80034D8D4 /* ScreenshotProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DAE0932804B0B80034D8D4 /* ScreenshotProcessor.swift */; };
|
D5DAE0942804B0B80034D8D4 /* ScreenshotProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DAE0932804B0B80034D8D4 /* ScreenshotProcessor.swift */; };
|
||||||
@@ -498,6 +507,8 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
0E0502592BEC83C500879B5C /* OperatingSystemVersion+Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OperatingSystemVersion+Comparable.swift"; sourceTree = "<group>"; };
|
||||||
|
0E05025B2BEC947000879B5C /* String+SideStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+SideStore.swift"; sourceTree = "<group>"; };
|
||||||
0E1A1F902AE36A9600364CAD /* bytearray.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = bytearray.c; path = src/bytearray.c; sourceTree = "<group>"; };
|
0E1A1F902AE36A9600364CAD /* bytearray.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = bytearray.c; path = src/bytearray.c; sourceTree = "<group>"; };
|
||||||
0E764E162ADFF5740043DD4E /* AltBackup.ipa */ = {isa = PBXFileReference; lastKnownFileType = file; name = AltBackup.ipa; path = AltStore/Resources/AltBackup.ipa; sourceTree = SOURCE_ROOT; };
|
0E764E162ADFF5740043DD4E /* AltBackup.ipa */ = {isa = PBXFileReference; lastKnownFileType = file; name = AltBackup.ipa; path = AltStore/Resources/AltBackup.ipa; sourceTree = SOURCE_ROOT; };
|
||||||
0EA166412ADFE0D1003015C1 /* jplist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = jplist.c; path = Dependencies/libplist/src/jplist.c; sourceTree = SOURCE_ROOT; };
|
0EA166412ADFE0D1003015C1 /* jplist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = jplist.c; path = Dependencies/libplist/src/jplist.c; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -533,11 +544,15 @@
|
|||||||
0EA166652ADFE122003015C1 /* hashtable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = hashtable.h; path = Dependencies/libplist/src/hashtable.h; sourceTree = SOURCE_ROOT; };
|
0EA166652ADFE122003015C1 /* hashtable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = hashtable.h; path = Dependencies/libplist/src/hashtable.h; sourceTree = SOURCE_ROOT; };
|
||||||
0EA166662ADFE122003015C1 /* base64.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = base64.h; path = Dependencies/libplist/src/base64.h; sourceTree = SOURCE_ROOT; };
|
0EA166662ADFE122003015C1 /* base64.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = base64.h; path = Dependencies/libplist/src/base64.h; sourceTree = SOURCE_ROOT; };
|
||||||
0EA166672ADFE122003015C1 /* strbuf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = strbuf.h; path = Dependencies/libplist/src/strbuf.h; sourceTree = SOURCE_ROOT; };
|
0EA166672ADFE122003015C1 /* strbuf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = strbuf.h; path = Dependencies/libplist/src/strbuf.h; sourceTree = SOURCE_ROOT; };
|
||||||
0EA4B9BB2AE4A3F6009209CE /* plist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = plist.c; path = Dependencies/Custom/libplist/src/plist.c; sourceTree = SOURCE_ROOT; };
|
0EA426392C2230150026D7FB /* AnisetteServerList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnisetteServerList.swift; sourceTree = "<group>"; };
|
||||||
|
0EA4B9BB2AE4A3F6009209CE /* plist.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = plist.c; path = Dependencies/libplist/src/plist.c; sourceTree = SOURCE_ROOT; };
|
||||||
|
0EE7FDC02BE8BC2100D1E390 /* ALTWrappedError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ALTWrappedError.m; sourceTree = "<group>"; };
|
||||||
|
0EE7FDC22BE8BC4200D1E390 /* ALTWrappedError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTWrappedError.h; sourceTree = "<group>"; };
|
||||||
|
0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALTLocalizedError.swift; sourceTree = "<group>"; };
|
||||||
|
0EE7FDCC2BE9124400D1E390 /* ErrorDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsViewController.swift; sourceTree = "<group>"; };
|
||||||
19104DB22909C06C00C49C7B /* libEmotionalDamage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libEmotionalDamage.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
19104DB22909C06C00C49C7B /* libEmotionalDamage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libEmotionalDamage.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
19104DB42909C06D00C49C7B /* EmotionalDamage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmotionalDamage.swift; sourceTree = "<group>"; };
|
19104DB42909C06D00C49C7B /* EmotionalDamage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmotionalDamage.swift; sourceTree = "<group>"; };
|
||||||
191E5FAB290A5D92001A3B7C /* libminimuxer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libminimuxer.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
191E5FAB290A5D92001A3B7C /* libminimuxer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libminimuxer.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
1920B04E2924AC8300744F60 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
|
||||||
19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTeamViewController.swift; sourceTree = "<group>"; };
|
19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTeamViewController.swift; sourceTree = "<group>"; };
|
||||||
9961EC2D29BE9F2E00AF2C6F /* minimuxer-helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "minimuxer-helpers.swift"; path = "Dependencies/minimuxer/minimuxer-helpers.swift"; sourceTree = SOURCE_ROOT; };
|
9961EC2D29BE9F2E00AF2C6F /* minimuxer-helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "minimuxer-helpers.swift"; path = "Dependencies/minimuxer/minimuxer-helpers.swift"; sourceTree = SOURCE_ROOT; };
|
||||||
99F87D1629D8E4C900B40039 /* SwiftBridgeCore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftBridgeCore.swift; path = Dependencies/minimuxer/SwiftBridgeCore.swift; sourceTree = SOURCE_ROOT; };
|
99F87D1629D8E4C900B40039 /* SwiftBridgeCore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftBridgeCore.swift; path = Dependencies/minimuxer/SwiftBridgeCore.swift; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -570,6 +585,7 @@
|
|||||||
B3C3960E284F4F9100DA9E2F /* AltStoreCore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltStoreCore.xcconfig; sourceTree = "<group>"; };
|
B3C3960E284F4F9100DA9E2F /* AltStoreCore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltStoreCore.xcconfig; sourceTree = "<group>"; };
|
||||||
B3C3960F284F53E900DA9E2F /* AltBackup.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltBackup.xcconfig; sourceTree = "<group>"; };
|
B3C3960F284F53E900DA9E2F /* AltBackup.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltBackup.xcconfig; sourceTree = "<group>"; };
|
||||||
B3EE16B52925E27D00B3B1F5 /* AnisetteManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnisetteManager.swift; sourceTree = "<group>"; };
|
B3EE16B52925E27D00B3B1F5 /* AnisetteManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnisetteManager.swift; sourceTree = "<group>"; };
|
||||||
|
BD4513AA2C6FA98C0052BCC0 /* AppExtensionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppExtensionView.swift; sourceTree = "<group>"; };
|
||||||
BF02419522F2199300129732 /* RefreshAttemptsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshAttemptsViewController.swift; sourceTree = "<group>"; };
|
BF02419522F2199300129732 /* RefreshAttemptsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshAttemptsViewController.swift; sourceTree = "<group>"; };
|
||||||
BF08858222DE795100DE9F1E /* MyAppsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppsViewController.swift; sourceTree = "<group>"; };
|
BF08858222DE795100DE9F1E /* MyAppsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppsViewController.swift; sourceTree = "<group>"; };
|
||||||
BF08858422DE7EC800DE9F1E /* UpdateCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCollectionViewCell.swift; sourceTree = "<group>"; };
|
BF08858422DE7EC800DE9F1E /* UpdateCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
@@ -784,7 +800,7 @@
|
|||||||
BFD2478B2284C4C300981D42 /* AppIconImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconImageView.swift; sourceTree = "<group>"; };
|
BFD2478B2284C4C300981D42 /* AppIconImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconImageView.swift; sourceTree = "<group>"; };
|
||||||
BFD2478E2284C8F900981D42 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
|
BFD2478E2284C8F900981D42 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = "<group>"; };
|
||||||
BFD2479E2284FBD000981D42 /* UIColor+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AltStore.swift"; sourceTree = "<group>"; };
|
BFD2479E2284FBD000981D42 /* UIColor+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AltStore.swift"; sourceTree = "<group>"; };
|
||||||
BFD44605241188C300EAB90A /* CodableServerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableServerError.swift; sourceTree = "<group>"; };
|
BFD44605241188C300EAB90A /* CodableError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableError.swift; sourceTree = "<group>"; };
|
||||||
BFD52BD222A06EFB000B7ED1 /* ALTConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTConstants.h; sourceTree = "<group>"; };
|
BFD52BD222A06EFB000B7ED1 /* ALTConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTConstants.h; sourceTree = "<group>"; };
|
||||||
BFD52C1D22A1A9EC000B7ED1 /* node.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node.c; path = Dependencies/libplist/libcnary/node.c; sourceTree = SOURCE_ROOT; };
|
BFD52C1D22A1A9EC000B7ED1 /* node.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node.c; path = Dependencies/libplist/libcnary/node.c; sourceTree = SOURCE_ROOT; };
|
||||||
BFD52C1E22A1A9EC000B7ED1 /* node_list.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node_list.c; path = Dependencies/libplist/libcnary/node_list.c; sourceTree = SOURCE_ROOT; };
|
BFD52C1E22A1A9EC000B7ED1 /* node_list.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = node_list.c; path = Dependencies/libplist/libcnary/node_list.c; sourceTree = SOURCE_ROOT; };
|
||||||
@@ -839,6 +855,7 @@
|
|||||||
D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogViewController.swift; sourceTree = "<group>"; };
|
D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogViewController.swift; sourceTree = "<group>"; };
|
||||||
D58916FD28C7C55C00E39C8B /* LoggedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedError.swift; sourceTree = "<group>"; };
|
D58916FD28C7C55C00E39C8B /* LoggedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedError.swift; sourceTree = "<group>"; };
|
||||||
D593F1932717749A006E82DE /* PatchAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchAppOperation.swift; sourceTree = "<group>"; };
|
D593F1932717749A006E82DE /* PatchAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PatchAppOperation.swift; sourceTree = "<group>"; };
|
||||||
|
D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearAppCacheOperation.swift; sourceTree = "<group>"; };
|
||||||
D5CA0C4A280E141900469595 /* ManagedPatron.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedPatron.swift; sourceTree = "<group>"; };
|
D5CA0C4A280E141900469595 /* ManagedPatron.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedPatron.swift; sourceTree = "<group>"; };
|
||||||
D5CA0C4C280E242500469595 /* AltStore 10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 10.xcdatamodel"; sourceTree = "<group>"; };
|
D5CA0C4C280E242500469595 /* AltStore 10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 10.xcdatamodel"; sourceTree = "<group>"; };
|
||||||
D5CA0C4D280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore9ToAltStore10.xcmappingmodel; sourceTree = "<group>"; };
|
D5CA0C4D280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore9ToAltStore10.xcmappingmodel; sourceTree = "<group>"; };
|
||||||
@@ -934,6 +951,16 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
0EE7FDBF2BE8BBBF00D1E390 /* Errors */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */,
|
||||||
|
0EE7FDC02BE8BC2100D1E390 /* ALTWrappedError.m */,
|
||||||
|
0EE7FDC22BE8BC4200D1E390 /* ALTWrappedError.h */,
|
||||||
|
);
|
||||||
|
path = Errors;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
19104DB32909C06D00C49C7B /* EmotionalDamage */ = {
|
19104DB32909C06D00C49C7B /* EmotionalDamage */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1059,7 +1086,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BF1E3128229F474900370A3C /* ServerProtocol.swift */,
|
BF1E3128229F474900370A3C /* ServerProtocol.swift */,
|
||||||
BFD44605241188C300EAB90A /* CodableServerError.swift */,
|
BFD44605241188C300EAB90A /* CodableError.swift */,
|
||||||
);
|
);
|
||||||
path = "Server Protocol";
|
path = "Server Protocol";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1072,6 +1099,7 @@
|
|||||||
BF18BFFF2485A75F00DD5981 /* Server Protocol */,
|
BF18BFFF2485A75F00DD5981 /* Server Protocol */,
|
||||||
BFF767CF2489AC240097E58C /* Connections */,
|
BFF767CF2489AC240097E58C /* Connections */,
|
||||||
BFF7C92D2578464D00E55F36 /* XPC */,
|
BFF7C92D2578464D00E55F36 /* XPC */,
|
||||||
|
0EE7FDBF2BE8BBBF00D1E390 /* Errors */,
|
||||||
BFF767C32489A6800097E58C /* Extensions */,
|
BFF767C32489A6800097E58C /* Extensions */,
|
||||||
BFF767C42489A6980097E58C /* Categories */,
|
BFF767C42489A6980097E58C /* Categories */,
|
||||||
);
|
);
|
||||||
@@ -1394,6 +1422,8 @@
|
|||||||
BF66EEE62501AED0007EE018 /* UIColor+Hex.swift */,
|
BF66EEE62501AED0007EE018 /* UIColor+Hex.swift */,
|
||||||
BF66EEE42501AED0007EE018 /* UserDefaults+AltStore.swift */,
|
BF66EEE42501AED0007EE018 /* UserDefaults+AltStore.swift */,
|
||||||
BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */,
|
BF6A531F246DC1B0004F59C8 /* FileManager+SharedDirectories.swift */,
|
||||||
|
0E0502592BEC83C500879B5C /* OperatingSystemVersion+Comparable.swift */,
|
||||||
|
0E05025B2BEC947000879B5C /* String+SideStore.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1573,7 +1603,6 @@
|
|||||||
BFD247962284D7C100981D42 /* Resources */,
|
BFD247962284D7C100981D42 /* Resources */,
|
||||||
BF6C8FA8242935CA00125131 /* Dependencies */,
|
BF6C8FA8242935CA00125131 /* Dependencies */,
|
||||||
BFD247972284D7D800981D42 /* Supporting Files */,
|
BFD247972284D7D800981D42 /* Supporting Files */,
|
||||||
1920B04E2924AC8300744F60 /* Settings.bundle */,
|
|
||||||
);
|
);
|
||||||
path = AltStore;
|
path = AltStore;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1596,6 +1625,7 @@
|
|||||||
children = (
|
children = (
|
||||||
BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */,
|
BF0C4EBC22A1BD8B009A2DD7 /* AppManager.swift */,
|
||||||
BF88F97124F8727D00BB75DF /* AppManagerErrors.swift */,
|
BF88F97124F8727D00BB75DF /* AppManagerErrors.swift */,
|
||||||
|
BD4513AA2C6FA98C0052BCC0 /* AppExtensionView.swift */,
|
||||||
);
|
);
|
||||||
path = "Managing Apps";
|
path = "Managing Apps";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1657,6 +1687,7 @@
|
|||||||
children = (
|
children = (
|
||||||
BFE60737231ADF49002B0E8E /* Settings.storyboard */,
|
BFE60737231ADF49002B0E8E /* Settings.storyboard */,
|
||||||
BFE60739231ADF82002B0E8E /* SettingsViewController.swift */,
|
BFE60739231ADF82002B0E8E /* SettingsViewController.swift */,
|
||||||
|
0EA426392C2230150026D7FB /* AnisetteServerList.swift */,
|
||||||
BFE6073F231AFD2A002B0E8E /* InsetGroupTableViewCell.swift */,
|
BFE6073F231AFD2A002B0E8E /* InsetGroupTableViewCell.swift */,
|
||||||
BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */,
|
BFE60741231B07E6002B0E8E /* SettingsHeaderFooterView.swift */,
|
||||||
BFE6073B231AE1E7002B0E8E /* SettingsHeaderFooterView.xib */,
|
BFE6073B231AE1E7002B0E8E /* SettingsHeaderFooterView.xib */,
|
||||||
@@ -1697,6 +1728,7 @@
|
|||||||
D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */,
|
D57F2C9026E0070200B9FA39 /* EnableJITOperation.swift */,
|
||||||
D5DAE0952804DF430034D8D4 /* UpdatePatronsOperation.swift */,
|
D5DAE0952804DF430034D8D4 /* UpdatePatronsOperation.swift */,
|
||||||
D5E1E7C028077DE90016FC96 /* FetchTrustedSourcesOperation.swift */,
|
D5E1E7C028077DE90016FC96 /* FetchTrustedSourcesOperation.swift */,
|
||||||
|
D5ACE84428E3B8450021CAB9 /* ClearAppCacheOperation.swift */,
|
||||||
BF7B44062725A4B8005288A4 /* Patch App */,
|
BF7B44062725A4B8005288A4 /* Patch App */,
|
||||||
);
|
);
|
||||||
path = Operations;
|
path = Operations;
|
||||||
@@ -1772,6 +1804,7 @@
|
|||||||
children = (
|
children = (
|
||||||
D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */,
|
D57FE84328C7DB7100216002 /* ErrorLogViewController.swift */,
|
||||||
D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */,
|
D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */,
|
||||||
|
0EE7FDCC2BE9124400D1E390 /* ErrorDetailsViewController.swift */,
|
||||||
);
|
);
|
||||||
path = "Error Log";
|
path = "Error Log";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1824,6 +1857,7 @@
|
|||||||
BFAECC5D2501B0BF00528F27 /* ALTConnection.h in Headers */,
|
BFAECC5D2501B0BF00528F27 /* ALTConnection.h in Headers */,
|
||||||
BF66EE942501AEBC007EE018 /* ALTAppPermission.h in Headers */,
|
BF66EE942501AEBC007EE018 /* ALTAppPermission.h in Headers */,
|
||||||
BFAECC602501B0BF00528F27 /* NSError+ALTServerError.h in Headers */,
|
BFAECC602501B0BF00528F27 /* NSError+ALTServerError.h in Headers */,
|
||||||
|
0EE7FDC82BE8CF4800D1E390 /* ALTWrappedError.h in Headers */,
|
||||||
BFAECC5E2501B0BF00528F27 /* CFNotificationName+AltStore.h in Headers */,
|
BFAECC5E2501B0BF00528F27 /* CFNotificationName+AltStore.h in Headers */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -2207,7 +2241,6 @@
|
|||||||
BFD2477A2284B9A700981D42 /* LaunchScreen.storyboard in Resources */,
|
BFD2477A2284B9A700981D42 /* LaunchScreen.storyboard in Resources */,
|
||||||
BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */,
|
BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */,
|
||||||
BFD247772284B9A700981D42 /* Assets.xcassets in Resources */,
|
BFD247772284B9A700981D42 /* Assets.xcassets in Resources */,
|
||||||
1920B04F2924AC8300744F60 /* Settings.bundle in Resources */,
|
|
||||||
BFF0B6922321A305007A79E1 /* AboutPatreonHeaderView.xib in Resources */,
|
BFF0B6922321A305007A79E1 /* AboutPatreonHeaderView.xib in Resources */,
|
||||||
BFB6B22423187A3D0022A802 /* NewsCollectionViewCell.xib in Resources */,
|
BFB6B22423187A3D0022A802 /* NewsCollectionViewCell.xib in Resources */,
|
||||||
BFD247752284B9A500981D42 /* Main.storyboard in Resources */,
|
BFD247752284B9A500981D42 /* Main.storyboard in Resources */,
|
||||||
@@ -2268,7 +2301,7 @@
|
|||||||
BF1FE358251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */,
|
BF1FE358251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */,
|
||||||
BFECAC8F24FD950E0077C41F /* Result+Conveniences.swift in Sources */,
|
BFECAC8F24FD950E0077C41F /* Result+Conveniences.swift in Sources */,
|
||||||
BF8CAE472489E772004D6CCE /* DaemonRequestHandler.swift in Sources */,
|
BF8CAE472489E772004D6CCE /* DaemonRequestHandler.swift in Sources */,
|
||||||
BFECAC8824FD950E0077C41F /* CodableServerError.swift in Sources */,
|
BFECAC8824FD950E0077C41F /* CodableError.swift in Sources */,
|
||||||
BFC712C32512D5F100AB5EBE /* XPCConnection.swift in Sources */,
|
BFC712C32512D5F100AB5EBE /* XPCConnection.swift in Sources */,
|
||||||
BFC712C52512D5F100AB5EBE /* XPCConnectionHandler.swift in Sources */,
|
BFC712C52512D5F100AB5EBE /* XPCConnectionHandler.swift in Sources */,
|
||||||
BFECAC8A24FD950E0077C41F /* ALTServerError+Conveniences.swift in Sources */,
|
BFECAC8A24FD950E0077C41F /* ALTServerError+Conveniences.swift in Sources */,
|
||||||
@@ -2365,8 +2398,8 @@
|
|||||||
BF580496246A3CB5008AE704 /* UIColor+AltBackup.swift in Sources */,
|
BF580496246A3CB5008AE704 /* UIColor+AltBackup.swift in Sources */,
|
||||||
BF580482246A28F7008AE704 /* ViewController.swift in Sources */,
|
BF580482246A28F7008AE704 /* ViewController.swift in Sources */,
|
||||||
BF44EEF0246B08BA002A52F2 /* BackupController.swift in Sources */,
|
BF44EEF0246B08BA002A52F2 /* BackupController.swift in Sources */,
|
||||||
|
0EE7FDC62BE8CEA300D1E390 /* ALTLocalizedError.swift in Sources */,
|
||||||
03F06CD52942C27E001C4D68 /* Bundle+AltStore.swift in Sources */,
|
03F06CD52942C27E001C4D68 /* Bundle+AltStore.swift in Sources */,
|
||||||
BF58049B246A432D008AE704 /* NSError+AltStore.swift in Sources */,
|
|
||||||
BF58047E246A28F7008AE704 /* AppDelegate.swift in Sources */,
|
BF58047E246A28F7008AE704 /* AppDelegate.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -2382,7 +2415,7 @@
|
|||||||
BF66EECD2501AECA007EE018 /* StoreAppPolicy.swift in Sources */,
|
BF66EECD2501AECA007EE018 /* StoreAppPolicy.swift in Sources */,
|
||||||
BF66EEE82501AED0007EE018 /* UserDefaults+AltStore.swift in Sources */,
|
BF66EEE82501AED0007EE018 /* UserDefaults+AltStore.swift in Sources */,
|
||||||
BF340E9A250AD39500A192CB /* ViewApp.intentdefinition in Sources */,
|
BF340E9A250AD39500A192CB /* ViewApp.intentdefinition in Sources */,
|
||||||
BFAECC522501B0A400528F27 /* CodableServerError.swift in Sources */,
|
BFAECC522501B0A400528F27 /* CodableError.swift in Sources */,
|
||||||
BF66EE9E2501AEC1007EE018 /* Fetchable.swift in Sources */,
|
BF66EE9E2501AEC1007EE018 /* Fetchable.swift in Sources */,
|
||||||
BF66EEDF2501AECA007EE018 /* PatreonAccount.swift in Sources */,
|
BF66EEDF2501AECA007EE018 /* PatreonAccount.swift in Sources */,
|
||||||
BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */,
|
BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */,
|
||||||
@@ -2390,11 +2423,14 @@
|
|||||||
BF66EE9D2501AEC1007EE018 /* AppProtocol.swift in Sources */,
|
BF66EE9D2501AEC1007EE018 /* AppProtocol.swift in Sources */,
|
||||||
BFC712C42512D5F100AB5EBE /* XPCConnection.swift in Sources */,
|
BFC712C42512D5F100AB5EBE /* XPCConnection.swift in Sources */,
|
||||||
D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */,
|
D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */,
|
||||||
|
0E05025A2BEC83C500879B5C /* OperatingSystemVersion+Comparable.swift in Sources */,
|
||||||
BF66EE8C2501AEB2007EE018 /* Keychain.swift in Sources */,
|
BF66EE8C2501AEB2007EE018 /* Keychain.swift in Sources */,
|
||||||
BF66EED42501AECA007EE018 /* AltStore5ToAltStore6.xcmappingmodel in Sources */,
|
BF66EED42501AECA007EE018 /* AltStore5ToAltStore6.xcmappingmodel in Sources */,
|
||||||
BF66EE972501AEBC007EE018 /* ALTAppPermission.m in Sources */,
|
BF66EE972501AEBC007EE018 /* ALTAppPermission.m in Sources */,
|
||||||
BFAECC552501B0A400528F27 /* Connection.swift in Sources */,
|
BFAECC552501B0A400528F27 /* Connection.swift in Sources */,
|
||||||
BF66EEDA2501AECA007EE018 /* RefreshAttempt.swift in Sources */,
|
BF66EEDA2501AECA007EE018 /* RefreshAttempt.swift in Sources */,
|
||||||
|
0E05025C2BEC947000879B5C /* String+SideStore.swift in Sources */,
|
||||||
|
0EE7FDCB2BE8D12B00D1E390 /* ALTLocalizedError.swift in Sources */,
|
||||||
BF66EEA92501AEC5007EE018 /* Tier.swift in Sources */,
|
BF66EEA92501AEC5007EE018 /* Tier.swift in Sources */,
|
||||||
BF66EEDB2501AECA007EE018 /* StoreApp.swift in Sources */,
|
BF66EEDB2501AECA007EE018 /* StoreApp.swift in Sources */,
|
||||||
BF66EEDE2501AECA007EE018 /* AppID.swift in Sources */,
|
BF66EEDE2501AECA007EE018 /* AppID.swift in Sources */,
|
||||||
@@ -2403,6 +2439,7 @@
|
|||||||
BF66EEDD2501AECA007EE018 /* AppPermission.swift in Sources */,
|
BF66EEDD2501AECA007EE018 /* AppPermission.swift in Sources */,
|
||||||
D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */,
|
D58916FE28C7C55C00E39C8B /* LoggedError.swift in Sources */,
|
||||||
BFBF331B2526762200B7B8C9 /* AltStore8ToAltStore9.xcmappingmodel in Sources */,
|
BFBF331B2526762200B7B8C9 /* AltStore8ToAltStore9.xcmappingmodel in Sources */,
|
||||||
|
0EE7FDC72BE8CF4100D1E390 /* ALTWrappedError.m in Sources */,
|
||||||
D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */,
|
D5CA0C4E280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel in Sources */,
|
||||||
BF989184250AACFC002ACF50 /* Date+RelativeDate.swift in Sources */,
|
BF989184250AACFC002ACF50 /* Date+RelativeDate.swift in Sources */,
|
||||||
BF66EE962501AEBC007EE018 /* ALTPatreonBenefitType.m in Sources */,
|
BF66EE962501AEBC007EE018 /* ALTPatreonBenefitType.m in Sources */,
|
||||||
@@ -2427,6 +2464,7 @@
|
|||||||
BF66EEEA2501AED0007EE018 /* UIColor+Hex.swift in Sources */,
|
BF66EEEA2501AED0007EE018 /* UIColor+Hex.swift in Sources */,
|
||||||
BF66EECC2501AECA007EE018 /* Source.swift in Sources */,
|
BF66EECC2501AECA007EE018 /* Source.swift in Sources */,
|
||||||
BF66EED72501AECA007EE018 /* InstalledApp.swift in Sources */,
|
BF66EED72501AECA007EE018 /* InstalledApp.swift in Sources */,
|
||||||
|
0EE7FDC92BE8D07400D1E390 /* NSError+AltStore.swift in Sources */,
|
||||||
BF66EECE2501AECA007EE018 /* InstalledAppPolicy.swift in Sources */,
|
BF66EECE2501AECA007EE018 /* InstalledAppPolicy.swift in Sources */,
|
||||||
BF1FE359251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */,
|
BF1FE359251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */,
|
||||||
BF66EEA62501AEC5007EE018 /* PatreonAPI.swift in Sources */,
|
BF66EEA62501AEC5007EE018 /* PatreonAPI.swift in Sources */,
|
||||||
@@ -2482,7 +2520,6 @@
|
|||||||
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */,
|
BF8F69C422E662D300049BA1 /* AppViewController.swift in Sources */,
|
||||||
BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */,
|
BFF0B68E23219520007A79E1 /* PatreonViewController.swift in Sources */,
|
||||||
BFF00D302501BD7D00746320 /* Intents.intentdefinition in Sources */,
|
BFF00D302501BD7D00746320 /* Intents.intentdefinition in Sources */,
|
||||||
BF6C336224197D700034FD24 /* NSError+AltStore.swift in Sources */,
|
|
||||||
D5DAE0942804B0B80034D8D4 /* ScreenshotProcessor.swift in Sources */,
|
D5DAE0942804B0B80034D8D4 /* ScreenshotProcessor.swift in Sources */,
|
||||||
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */,
|
BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */,
|
||||||
BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */,
|
BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */,
|
||||||
@@ -2498,12 +2535,15 @@
|
|||||||
BF6C8FAE2429597900125131 /* BannerCollectionViewCell.swift in Sources */,
|
BF6C8FAE2429597900125131 /* BannerCollectionViewCell.swift in Sources */,
|
||||||
BF6F439223644C6E00A0B879 /* RefreshAltStoreViewController.swift in Sources */,
|
BF6F439223644C6E00A0B879 /* RefreshAltStoreViewController.swift in Sources */,
|
||||||
BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */,
|
BFE60742231B07E6002B0E8E /* SettingsHeaderFooterView.swift in Sources */,
|
||||||
|
BD4513AB2C6FA98C0052BCC0 /* AppExtensionView.swift in Sources */,
|
||||||
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */,
|
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */,
|
||||||
BFA8172B23C5633D001B5953 /* FetchAnisetteDataOperation.swift in Sources */,
|
BFA8172B23C5633D001B5953 /* FetchAnisetteDataOperation.swift in Sources */,
|
||||||
BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */,
|
BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */,
|
||||||
BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */,
|
BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */,
|
||||||
BF41B808233433C100C593A3 /* LoadingState.swift in Sources */,
|
BF41B808233433C100C593A3 /* LoadingState.swift in Sources */,
|
||||||
BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */,
|
BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */,
|
||||||
|
D5ACE84528E3B8450021CAB9 /* ClearAppCacheOperation.swift in Sources */,
|
||||||
|
0EA4263A2C2230150026D7FB /* AnisetteServerList.swift in Sources */,
|
||||||
D5F2F6A92720B7C20081CCF5 /* PatchViewController.swift in Sources */,
|
D5F2F6A92720B7C20081CCF5 /* PatchViewController.swift in Sources */,
|
||||||
B39F16132918D7C5002E9404 /* Consts.swift in Sources */,
|
B39F16132918D7C5002E9404 /* Consts.swift in Sources */,
|
||||||
BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */,
|
BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */,
|
||||||
@@ -2529,9 +2569,11 @@
|
|||||||
BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */,
|
BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */,
|
||||||
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
||||||
BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */,
|
BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */,
|
||||||
|
0EE7FDC42BE8BC7900D1E390 /* ALTLocalizedError.swift in Sources */,
|
||||||
BFF00D342501BDCF00746320 /* IntentHandler.swift in Sources */,
|
BFF00D342501BDCF00746320 /* IntentHandler.swift in Sources */,
|
||||||
BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */,
|
BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */,
|
||||||
BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */,
|
BFF0B6942321CB85007A79E1 /* AuthenticationViewController.swift in Sources */,
|
||||||
|
0EE7FDCD2BE9124400D1E390 /* ErrorDetailsViewController.swift in Sources */,
|
||||||
BF3432FB246B894F0052F4A1 /* BackupAppOperation.swift in Sources */,
|
BF3432FB246B894F0052F4A1 /* BackupAppOperation.swift in Sources */,
|
||||||
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */,
|
BF9ABA4922DD0742008935CF /* ScreenshotCollectionViewCell.swift in Sources */,
|
||||||
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */,
|
BF9ABA4D22DD16DE008935CF /* PillButton.swift in Sources */,
|
||||||
@@ -2851,11 +2893,6 @@
|
|||||||
PRODUCT_NAME = imobiledevice;
|
PRODUCT_NAME = imobiledevice;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SYSTEM_HEADER_SEARCH_PATHS = (
|
|
||||||
"\"$(SRCROOT)/Dependencies/AltSign/Dependencies\"",
|
|
||||||
"\"$(SRCROOT)/Dependencies/libplist/include\"",
|
|
||||||
"\"$(SRCROOT)/Dependencies/libplist/libcnary/include\"",
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -2896,11 +2933,6 @@
|
|||||||
PRODUCT_NAME = imobiledevice;
|
PRODUCT_NAME = imobiledevice;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SYSTEM_HEADER_SEARCH_PATHS = (
|
|
||||||
"\"$(SRCROOT)/Dependencies/AltSign/Dependencies\"",
|
|
||||||
"\"$(SRCROOT)/Dependencies/libplist/include\"",
|
|
||||||
"\"$(SRCROOT)/Dependencies/libplist/libcnary/include\"",
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
@@ -2971,6 +3003,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@loader_path/Frameworks",
|
"@loader_path/Frameworks",
|
||||||
);
|
);
|
||||||
|
LLVM_LTO = YES_THIN;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@@ -2999,6 +3032,7 @@
|
|||||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
DYLIB_CURRENT_VERSION = 1;
|
DYLIB_CURRENT_VERSION = 1;
|
||||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||||
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
INFOPLIST_FILE = AltStoreCore/Info.plist;
|
INFOPLIST_FILE = AltStoreCore/Info.plist;
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
@@ -3007,6 +3041,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@loader_path/Frameworks",
|
"@loader_path/Frameworks",
|
||||||
);
|
);
|
||||||
|
LLVM_LTO = YES_THIN;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@@ -3116,6 +3151,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
EAGER_LINKING = YES;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
@@ -3133,6 +3169,7 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||||
|
LLVM_LTO = YES_THIN;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
@@ -3143,6 +3180,7 @@
|
|||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx";
|
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG BETA";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG BETA";
|
||||||
|
SWIFT_ENFORCE_EXCLUSIVE_ACCESS = "debug-only";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SYSTEM_HEADER_SEARCH_PATHS = "\"$(SRCROOT)/Dependencies/AltSign/Dependencies\"";
|
SYSTEM_HEADER_SEARCH_PATHS = "\"$(SRCROOT)/Dependencies/AltSign/Dependencies\"";
|
||||||
};
|
};
|
||||||
@@ -3184,10 +3222,12 @@
|
|||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
EAGER_LINKING = YES;
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GCC_NO_COMMON_BLOCKS = YES;
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
@@ -3195,6 +3235,7 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
|
||||||
|
LLVM_LTO = YES_THIN;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
OTHER_CPLUSPLUSFLAGS = (
|
OTHER_CPLUSPLUSFLAGS = (
|
||||||
@@ -3204,8 +3245,9 @@
|
|||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx";
|
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG BETA";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG BETA";
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = singlefile;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
SWIFT_ENFORCE_EXCLUSIVE_ACCESS = "debug-only";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Osize";
|
||||||
SYSTEM_HEADER_SEARCH_PATHS = "\"$(SRCROOT)/Dependencies/AltSign/Dependencies\"";
|
SYSTEM_HEADER_SEARCH_PATHS = "\"$(SRCROOT)/Dependencies/AltSign/Dependencies\"";
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
@@ -3215,7 +3257,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = B3C3960B284F4C9800DA9E2F /* AltStore.xcconfig */;
|
baseConfigurationReference = B3C3960B284F4C9800DA9E2F /* AltStore.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = AltStore/AltStore.entitlements;
|
CODE_SIGN_ENTITLEMENTS = AltStore/AltStore.entitlements;
|
||||||
@@ -3223,8 +3265,10 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
|
GCC_UNROLL_LOOPS = YES;
|
||||||
INFOPLIST_FILE = AltStore/Info.plist;
|
INFOPLIST_FILE = AltStore/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
|
LD_EXPORT_SYMBOLS = NO;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -3234,11 +3278,13 @@
|
|||||||
"$(PROJECT_DIR)/Dependencies/libfragmentzip",
|
"$(PROJECT_DIR)/Dependencies/libfragmentzip",
|
||||||
"$(PROJECT_DIR)/Dependencies/libcurl",
|
"$(PROJECT_DIR)/Dependencies/libcurl",
|
||||||
);
|
);
|
||||||
|
LLVM_LTO = YES_THIN;
|
||||||
OTHER_LDFLAGS = "";
|
OTHER_LDFLAGS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
||||||
|
SWIFT_ENFORCE_EXCLUSIVE_ACCESS = "debug-only";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "AltStore/AltStore-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "AltStore/AltStore-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -3250,7 +3296,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = B3C3960B284F4C9800DA9E2F /* AltStore.xcconfig */;
|
baseConfigurationReference = B3C3960B284F4C9800DA9E2F /* AltStore.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = AltStore/AltStore.entitlements;
|
CODE_SIGN_ENTITLEMENTS = AltStore/AltStore.entitlements;
|
||||||
@@ -3258,8 +3304,11 @@
|
|||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
|
GCC_UNROLL_LOOPS = YES;
|
||||||
INFOPLIST_FILE = AltStore/Info.plist;
|
INFOPLIST_FILE = AltStore/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
|
LD_EXPORT_SYMBOLS = NO;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -3269,12 +3318,16 @@
|
|||||||
"$(PROJECT_DIR)/Dependencies/libfragmentzip",
|
"$(PROJECT_DIR)/Dependencies/libfragmentzip",
|
||||||
"$(PROJECT_DIR)/Dependencies/libcurl",
|
"$(PROJECT_DIR)/Dependencies/libcurl",
|
||||||
);
|
);
|
||||||
|
LLVM_LTO = YES_THIN;
|
||||||
OTHER_LDFLAGS = "";
|
OTHER_LDFLAGS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
|
||||||
|
SWIFT_COMPILATION_MODE = singlefile;
|
||||||
|
SWIFT_ENFORCE_EXCLUSIVE_ACCESS = "debug-only";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "AltStore/AltStore-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "AltStore/AltStore-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Osize";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
{
|
|
||||||
"pins" : [
|
|
||||||
{
|
|
||||||
"identity" : "altsign",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/SideStore/AltSign",
|
|
||||||
"state" : {
|
|
||||||
"branch" : "master",
|
|
||||||
"revision" : "cc6189f0f7cd8e5bd24943af9322e0ff9420e9f4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "appcenter-sdk-apple",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/microsoft/appcenter-sdk-apple.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "b2dc99cfedead0bad4e6573d86c5228c89cff332",
|
|
||||||
"version" : "4.4.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "imobiledevice.swift",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/SideStore/iMobileDevice.swift",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "74e481106dd155c0cd21bca6795fd9fe5f751654",
|
|
||||||
"version" : "1.0.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "keychainaccess",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
|
|
||||||
"version" : "4.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "launchatlogin",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/sindresorhus/LaunchAtLogin.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "e8171b3e38a2816f579f58f3dac1522aa39efe41",
|
|
||||||
"version" : "4.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "nuke",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/kean/Nuke.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "9318d02a8a6d20af56505c9673261c1fd3b3aebe",
|
|
||||||
"version" : "7.6.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "openssl",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/krzyzanowskim/OpenSSL",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "0faf71a188bcfdf0245cab42886b9b240ca71c52",
|
|
||||||
"version" : "1.1.2200"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "plcrashreporter",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/microsoft/PLCrashReporter.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "81cdec2b3827feb03286cb297f4c501a8eb98df1",
|
|
||||||
"version" : "1.10.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "semanticversion",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/SwiftPackageIndex/SemanticVersion.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "a70840d5fca686ae3bd2fcf8aecc5ded0bd4f125",
|
|
||||||
"version" : "0.3.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "sparkle",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/sparkle-project/Sparkle.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "f0ceaf5cc9f3f23daa0ccb6dcebd79fc96ccc7d9",
|
|
||||||
"version" : "2.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "starscream",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/daltoniam/Starscream.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "ac6c0fc9da221873e01bd1a0d4818498a71eef33",
|
|
||||||
"version" : "4.0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "stprivilegedtask",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/JoeMatt/STPrivilegedTask.git",
|
|
||||||
"state" : {
|
|
||||||
"branch" : "master",
|
|
||||||
"revision" : "10a9150ef32d444af326beba76356ae9af95a3e7"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"version" : 2
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,6 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>aps-environment</key>
|
|
||||||
<string>development</string>
|
|
||||||
<key>com.apple.developer.siri</key>
|
<key>com.apple.developer.siri</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ final class AppContentViewController: UITableViewController
|
|||||||
self.subtitleLabel.text = self.app.subtitle
|
self.subtitleLabel.text = self.app.subtitle
|
||||||
self.descriptionTextView.text = self.app.localizedDescription
|
self.descriptionTextView.text = self.app.localizedDescription
|
||||||
|
|
||||||
if let version = self.app.latestVersion
|
if let version = self.app.latestAvailableVersion
|
||||||
{
|
{
|
||||||
self.versionDescriptionTextView.text = version.localizedDescription
|
self.versionDescriptionTextView.text = version.localizedDescription
|
||||||
self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), version.version)
|
self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), version.version)
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ private extension AppViewController
|
|||||||
button.progress = progress
|
button.progress = progress
|
||||||
}
|
}
|
||||||
|
|
||||||
if let versionDate = self.app.latestVersion?.date, versionDate > Date()
|
if let versionDate = self.app.latestAvailableVersion?.date, versionDate > Date()
|
||||||
{
|
{
|
||||||
self.bannerView.button.countdownDate = versionDate
|
self.bannerView.button.countdownDate = versionDate
|
||||||
self.navigationBarDownloadButton.countdownDate = versionDate
|
self.navigationBarDownloadButton.countdownDate = versionDate
|
||||||
@@ -510,7 +510,7 @@ extension AppViewController
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error, opensLog: true)
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,14 +90,21 @@ private extension AppIDsViewController
|
|||||||
cell.bannerView.button.isUserInteractionEnabled = false
|
cell.bannerView.button.isUserInteractionEnabled = false
|
||||||
|
|
||||||
cell.bannerView.buttonLabel.isHidden = false
|
cell.bannerView.buttonLabel.isHidden = false
|
||||||
|
|
||||||
let currentDate = Date()
|
let currentDate = Date()
|
||||||
|
|
||||||
let numberOfDays = expirationDate.numberOfCalendarDays(since: currentDate)
|
let formatter = DateComponentsFormatter()
|
||||||
let numberOfDaysText = (numberOfDays == 1) ? NSLocalizedString("1 day", comment: "") : String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
formatter.unitsStyle = .full
|
||||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
formatter.includesApproximationPhrase = false
|
||||||
|
formatter.includesTimeRemainingPhrase = false
|
||||||
|
formatter.allowedUnits = [.minute, .hour, .day]
|
||||||
|
formatter.maximumUnitCount = 1
|
||||||
|
|
||||||
attributedAccessibilityLabel.mutableString.append(String(format: NSLocalizedString("Expires in %@.", comment: ""), numberOfDaysText) + " ")
|
cell.bannerView.button.setTitle((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")).uppercased(), for: .normal)
|
||||||
|
|
||||||
|
// formatter.includesTimeRemainingPhrase = true
|
||||||
|
|
||||||
|
// attributedAccessibilityLabel.mutableString.append((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " ")
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
// Register default settings before doing anything else.
|
// Register default settings before doing anything else.
|
||||||
UserDefaults.registerDefaults()
|
UserDefaults.registerDefaults()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DatabaseManager.shared.start { (error) in
|
DatabaseManager.shared.start { (error) in
|
||||||
if let error = error
|
if let error = error
|
||||||
{
|
{
|
||||||
@@ -380,7 +382,7 @@ private extension AppDelegate
|
|||||||
for update in updates
|
for update in updates
|
||||||
{
|
{
|
||||||
guard !previousUpdates.contains(where: { $0[#keyPath(InstalledApp.bundleIdentifier)] == update.bundleIdentifier }) else { continue }
|
guard !previousUpdates.contains(where: { $0[#keyPath(InstalledApp.bundleIdentifier)] == update.bundleIdentifier }) else { continue }
|
||||||
guard let storeApp = update.storeApp, let version = storeApp.version else { continue }
|
guard let storeApp = update.storeApp, let version = storeApp.latestSupportedVersion else { continue }
|
||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
content.title = NSLocalizedString("New Update Available", comment: "")
|
content.title = NSLocalizedString("New Update Available", comment: "")
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
|
||||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
<scene sceneID="lNR-II-WoW">
|
<scene sceneID="lNR-II-WoW">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController storyboardIdentifier="navigationController" id="ZTo-53-dSL" sceneMemberID="viewController">
|
<navigationController storyboardIdentifier="navigationController" id="ZTo-53-dSL" sceneMemberID="viewController">
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Aej-RF-PfV" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Aej-RF-PfV" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<color key="barTintColor" name="SettingsBackground"/>
|
<color key="barTintColor" name="SettingsBackground"/>
|
||||||
@@ -36,19 +36,19 @@
|
|||||||
<!--Authentication View Controller-->
|
<!--Authentication View Controller-->
|
||||||
<scene sceneID="OCd-xc-Ms7">
|
<scene sceneID="OCd-xc-Ms7">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController storyboardIdentifier="authenticationViewController" id="yO1-iT-7NP" customClass="AuthenticationViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController storyboardIdentifier="authenticationViewController" id="yO1-iT-7NP" customClass="AuthenticationViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" contentMode="scaleToFill" id="mjy-4S-hyH">
|
<view key="view" contentMode="scaleToFill" id="mjy-4S-hyH">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oyW-Fd-ojD" userLabel="Sizing View">
|
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oyW-Fd-ojD" userLabel="Sizing View">
|
||||||
<rect key="frame" x="0.0" y="44" width="375" height="623"/>
|
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
|
||||||
</view>
|
</view>
|
||||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" alwaysBounceVertical="YES" indicatorStyle="white" keyboardDismissMode="onDrag" translatesAutoresizingMaskIntoConstraints="NO" id="WXx-hX-AXv">
|
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" alwaysBounceVertical="YES" indicatorStyle="white" keyboardDismissMode="onDrag" translatesAutoresizingMaskIntoConstraints="NO" id="WXx-hX-AXv">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2wp-qG-f0Z">
|
<view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2wp-qG-f0Z">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="623"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="603"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" spacing="50" translatesAutoresizingMaskIntoConstraints="NO" id="YmX-7v-pxh">
|
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" spacing="50" translatesAutoresizingMaskIntoConstraints="NO" id="YmX-7v-pxh">
|
||||||
<rect key="frame" x="16" y="6" width="343" height="359.5"/>
|
<rect key="frame" x="16" y="6" width="343" height="359.5"/>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="67.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="343" height="67.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Welcome to SideStore." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="EI2-V3-zQZ">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Welcome to SideStore." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="EI2-V3-zQZ">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="332" height="41"/>
|
<rect key="frame" x="0.0" y="0.0" width="343" height="41"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -179,7 +179,7 @@
|
|||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="250" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="DBk-rT-ZE8">
|
<stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="250" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="DBk-rT-ZE8">
|
||||||
<rect key="frame" x="16" y="518.5" width="343" height="96.5"/>
|
<rect key="frame" x="16" y="498.5" width="343" height="96.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Why do we need this?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p9U-0q-Kn8">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Why do we need this?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p9U-0q-Kn8">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="343" height="20.5"/>
|
||||||
@@ -198,6 +198,10 @@
|
|||||||
</stackView>
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
|
<constraint firstItem="DBk-rT-ZE8" firstAttribute="leading" secondItem="2wp-qG-f0Z" secondAttribute="leadingMargin" id="5AT-nV-ZP9"/>
|
||||||
|
<constraint firstAttribute="bottomMargin" secondItem="DBk-rT-ZE8" secondAttribute="bottom" id="HgY-oY-8KM"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="DBk-rT-ZE8" secondAttribute="trailing" id="VCf-bW-2K4"/>
|
||||||
|
<constraint firstItem="YmX-7v-pxh" firstAttribute="top" secondItem="2wp-qG-f0Z" secondAttribute="top" constant="6" id="iUr-Nd-tkt"/>
|
||||||
<constraint firstItem="DBk-rT-ZE8" firstAttribute="top" relation="greaterThanOrEqual" secondItem="YmX-7v-pxh" secondAttribute="bottom" constant="8" symbolic="YES" id="zTU-eY-DWd"/>
|
<constraint firstItem="DBk-rT-ZE8" firstAttribute="top" relation="greaterThanOrEqual" secondItem="YmX-7v-pxh" secondAttribute="bottom" constant="8" symbolic="YES" id="zTU-eY-DWd"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
@@ -215,19 +219,15 @@
|
|||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="bottom" secondItem="WXx-hX-AXv" secondAttribute="bottom" id="0jL-Ky-ju6"/>
|
<constraint firstAttribute="bottom" secondItem="WXx-hX-AXv" secondAttribute="bottom" id="0jL-Ky-ju6"/>
|
||||||
<constraint firstAttribute="leadingMargin" secondItem="YmX-7v-pxh" secondAttribute="leading" id="2PO-lG-dmB"/>
|
<constraint firstAttribute="leadingMargin" secondItem="YmX-7v-pxh" secondAttribute="leading" id="2PO-lG-dmB"/>
|
||||||
<constraint firstItem="DBk-rT-ZE8" firstAttribute="leading" secondItem="2wp-qG-f0Z" secondAttribute="leadingMargin" id="5AT-nV-ZP9"/>
|
|
||||||
<constraint firstItem="oyW-Fd-ojD" firstAttribute="top" secondItem="zMn-DV-fpy" secondAttribute="top" id="730-db-ukB"/>
|
<constraint firstItem="oyW-Fd-ojD" firstAttribute="top" secondItem="zMn-DV-fpy" secondAttribute="top" id="730-db-ukB"/>
|
||||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="bottomMargin" secondItem="DBk-rT-ZE8" secondAttribute="bottom" id="HgY-oY-8KM"/>
|
|
||||||
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="oyW-Fd-ojD" secondAttribute="trailing" id="KGE-CN-SWf"/>
|
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="oyW-Fd-ojD" secondAttribute="trailing" id="KGE-CN-SWf"/>
|
||||||
<constraint firstItem="WXx-hX-AXv" firstAttribute="top" secondItem="mjy-4S-hyH" secondAttribute="top" id="LPQ-bF-ic0"/>
|
<constraint firstItem="WXx-hX-AXv" firstAttribute="top" secondItem="mjy-4S-hyH" secondAttribute="top" id="LPQ-bF-ic0"/>
|
||||||
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="WXx-hX-AXv" secondAttribute="trailing" id="MG7-A6-pKp"/>
|
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="WXx-hX-AXv" secondAttribute="trailing" id="MG7-A6-pKp"/>
|
||||||
<constraint firstAttribute="trailingMargin" secondItem="YmX-7v-pxh" secondAttribute="trailing" id="O4T-nu-o3e"/>
|
<constraint firstAttribute="trailingMargin" secondItem="YmX-7v-pxh" secondAttribute="trailing" id="O4T-nu-o3e"/>
|
||||||
<constraint firstItem="zMn-DV-fpy" firstAttribute="bottom" secondItem="oyW-Fd-ojD" secondAttribute="bottom" id="PuX-ab-cEq"/>
|
<constraint firstItem="zMn-DV-fpy" firstAttribute="bottom" secondItem="oyW-Fd-ojD" secondAttribute="bottom" id="PuX-ab-cEq"/>
|
||||||
<constraint firstItem="oyW-Fd-ojD" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="SzC-gC-Nvi"/>
|
<constraint firstItem="oyW-Fd-ojD" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="SzC-gC-Nvi"/>
|
||||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="trailingMargin" secondItem="DBk-rT-ZE8" secondAttribute="trailing" id="VCf-bW-2K4"/>
|
|
||||||
<constraint firstItem="WXx-hX-AXv" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="d08-zF-5X6"/>
|
<constraint firstItem="WXx-hX-AXv" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="d08-zF-5X6"/>
|
||||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="height" secondItem="oyW-Fd-ojD" secondAttribute="height" id="dFN-pw-TWt"/>
|
<constraint firstItem="2wp-qG-f0Z" firstAttribute="height" secondItem="oyW-Fd-ojD" secondAttribute="height" id="dFN-pw-TWt"/>
|
||||||
<constraint firstItem="YmX-7v-pxh" firstAttribute="top" secondItem="2wp-qG-f0Z" secondAttribute="top" constant="6" id="iUr-Nd-tkt"/>
|
|
||||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="width" secondItem="oyW-Fd-ojD" secondAttribute="width" id="rYO-GN-0Lk"/>
|
<constraint firstItem="2wp-qG-f0Z" firstAttribute="width" secondItem="oyW-Fd-ojD" secondAttribute="width" id="rYO-GN-0Lk"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
@@ -258,13 +258,13 @@
|
|||||||
<!--How it works-->
|
<!--How it works-->
|
||||||
<scene sceneID="dMt-EA-SGy">
|
<scene sceneID="dMt-EA-SGy">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController storyboardIdentifier="instructionsViewController" hidesBottomBarWhenPushed="YES" id="aFi-fb-W0B" customClass="InstructionsViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController storyboardIdentifier="instructionsViewController" hidesBottomBarWhenPushed="YES" id="aFi-fb-W0B" customClass="InstructionsViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="Otz-hn-WGS">
|
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="Otz-hn-WGS">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="bp6-55-IG2">
|
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="bp6-55-IG2">
|
||||||
<rect key="frame" x="0.0" y="44" width="375" height="564"/>
|
<rect key="frame" x="0.0" y="64" width="375" height="544"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="FjP-tm-w7K">
|
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="FjP-tm-w7K">
|
||||||
<rect key="frame" x="16" y="35" width="343" height="95.5"/>
|
<rect key="frame" x="16" y="35" width="343" height="95.5"/>
|
||||||
@@ -298,7 +298,7 @@
|
|||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="LpI-Jt-SzX">
|
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="LpI-Jt-SzX">
|
||||||
<rect key="frame" x="16" y="168" width="343" height="95.5"/>
|
<rect key="frame" x="16" y="161" width="343" height="95.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0LW-eE-qHa">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0LW-eE-qHa">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
||||||
@@ -310,7 +310,7 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="dMu-eg-gIO">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="dMu-eg-gIO">
|
||||||
<rect key="frame" x="79" y="17" width="264" height="61.5"/>
|
<rect key="frame" x="79" y="17.5" width="264" height="60.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Connect to Wi-Fi and VPN" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="esj-pD-D4A">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Connect to Wi-Fi and VPN" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="esj-pD-D4A">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
||||||
@@ -319,7 +319,7 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable SideStore VPN in Wireguard and be able to use Sidestore on the go." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="4rk-ge-FSj">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable SideStore VPN in Wireguard and be able to use Sidestore on the go." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="4rk-ge-FSj">
|
||||||
<rect key="frame" x="0.0" y="25.5" width="264" height="36"/>
|
<rect key="frame" x="0.0" y="25.5" width="264" height="35"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -329,7 +329,7 @@
|
|||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="tfb-ja-9UC">
|
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="tfb-ja-9UC">
|
||||||
<rect key="frame" x="16" y="300.5" width="343" height="95.5"/>
|
<rect key="frame" x="16" y="287.5" width="343" height="95.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nVr-El-Csi">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nVr-El-Csi">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
||||||
@@ -341,7 +341,7 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="z6Y-zi-teL">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="z6Y-zi-teL">
|
||||||
<rect key="frame" x="79" y="16" width="264" height="64"/>
|
<rect key="frame" x="79" y="15.5" width="264" height="64"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Download Apps" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="JeJ-bk-UCA">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Download Apps" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="JeJ-bk-UCA">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
||||||
@@ -360,7 +360,7 @@
|
|||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="X3r-G1-vf2">
|
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="X3r-G1-vf2">
|
||||||
<rect key="frame" x="16" y="433.5" width="343" height="95.5"/>
|
<rect key="frame" x="16" y="413.5" width="343" height="95.5"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="4" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="i2U-NL-plG">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="4" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="i2U-NL-plG">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
||||||
@@ -434,7 +434,7 @@
|
|||||||
<!--Refresh AltStore-->
|
<!--Refresh AltStore-->
|
||||||
<scene sceneID="9Vh-dM-OqX">
|
<scene sceneID="9Vh-dM-OqX">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController storyboardIdentifier="refreshAltStoreViewController" id="aoK-yE-UVT" customClass="RefreshAltStoreViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController storyboardIdentifier="refreshAltStoreViewController" id="aoK-yE-UVT" customClass="RefreshAltStoreViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="R83-kV-365">
|
<view key="view" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" id="R83-kV-365">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
@@ -445,7 +445,7 @@
|
|||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="tDQ-ao-1Jg">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="tDQ-ao-1Jg">
|
||||||
<rect key="frame" x="16" y="570" width="343" height="89"/>
|
<rect key="frame" x="16" y="570" width="343" height="89"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xcg-hT-tDe" customClass="PillButton" customModule="AltStore" customModuleProvider="target">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xcg-hT-tDe" customClass="PillButton" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="343" height="51"/>
|
||||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
@@ -493,12 +493,12 @@
|
|||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Chr-7g-qEw" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="Chr-7g-qEw" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="2967" y="736"/>
|
<point key="canvasLocation" x="3025" y="734"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Select a Team-->
|
<!--Select a Team-->
|
||||||
<scene sceneID="ioQ-WB-CLJ">
|
<scene sceneID="ioQ-WB-CLJ">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController storyboardIdentifier="selectTeamViewController" hidesBottomBarWhenPushed="YES" id="kOD-4P-a6L" customClass="SelectTeamViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController storyboardIdentifier="selectTeamViewController" hidesBottomBarWhenPushed="YES" id="kOD-4P-a6L" customClass="SelectTeamViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" indicatorStyle="white" dataMode="prototypes" style="grouped" separatorStyle="none" rowHeight="60" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="fWW-kX-ifH">
|
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" indicatorStyle="white" dataMode="prototypes" style="grouped" separatorStyle="none" rowHeight="60" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="fWW-kX-ifH">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
@@ -506,11 +506,11 @@
|
|||||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<prototypes>
|
<prototypes>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="TeamCell" textLabel="6ip-34-gmM" detailTextLabel="knk-Wf-PKf" style="IBUITableViewCellStyleSubtitle" id="qeQ-eb-2SC" customClass="InsetGroupTableViewCell" customModule="AltStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="TeamCell" textLabel="6ip-34-gmM" detailTextLabel="knk-Wf-PKf" style="IBUITableViewCellStyleSubtitle" id="qeQ-eb-2SC" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="55.5" width="375" height="60"/>
|
<rect key="frame" x="0.0" y="55.5" width="375" height="60"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="qeQ-eb-2SC" id="bT4-Fc-u6I">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="qeQ-eb-2SC" id="bT4-Fc-u6I">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="334" height="60"/>
|
<rect key="frame" x="0.0" y="0.0" width="334.5" height="60"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Team 1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="6ip-34-gmM">
|
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Team 1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="6ip-34-gmM">
|
||||||
@@ -550,20 +550,19 @@
|
|||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="yH5-jU-aez" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="yH5-jU-aez" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="1401" y="734"/>
|
<point key="canvasLocation" x="2114" y="734"/>
|
||||||
|
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<color key="tintColor" name="Primary"/>
|
<color key="tintColor" name="Primary"/>
|
||||||
<resources>
|
<resources>
|
||||||
<namedColor name="Primary">
|
<namedColor name="Primary">
|
||||||
<color red="0.0040000001899898052" green="0.50199997425079346" blue="0.51800000667572021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.64313725490196083" green="0.019607843137254902" blue="0.98039215686274506" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
<namedColor name="SettingsBackground">
|
<namedColor name="SettingsBackground">
|
||||||
<color red="0.0039215686274509803" green="0.50196078431372548" blue="0.51764705882352946" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.45098039215686275" green="0.015686274509803921" blue="0.68627450980392157" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
<namedColor name="SettingsHighlighted">
|
<namedColor name="SettingsHighlighted">
|
||||||
<color red="0.0080000003799796104" green="0.32199999690055847" blue="0.40400001406669617" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.38823529411764707" green="0.011764705882352941" blue="0.58823529411764708" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -108,11 +108,9 @@ private extension AuthenticationViewController
|
|||||||
|
|
||||||
case .failure(let error as NSError):
|
case .failure(let error as NSError):
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let error = error.withLocalizedFailure(NSLocalizedString("Failed to Log In", comment: ""))
|
let error = error.withLocalizedTitle(NSLocalizedString("Failed to Log In", comment: ""))
|
||||||
|
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error)
|
||||||
toastView.textLabel.textColor = .altPink
|
|
||||||
toastView.detailTextLabel.textColor = .altPink
|
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
self.toastView = toastView
|
self.toastView = toastView
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21223" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="wKh-xq-NuP">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="wKh-xq-NuP">
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21204"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
|
||||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
@@ -356,8 +356,8 @@
|
|||||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="ewH-gi-pyW">
|
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="ewH-gi-pyW">
|
||||||
<rect key="frame" x="0.0" y="30.5" width="335" height="17"/>
|
<rect key="frame" x="0.0" y="30.5" width="335" height="17"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Version 4.4.2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7E0-TV-G4l">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Version 0.5.6" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7E0-TV-G4l">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="84.5" height="17"/>
|
<rect key="frame" x="0.0" y="0.0" width="84" height="17"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -596,7 +596,7 @@ World</string>
|
|||||||
<tabBarItem key="tabBarItem" title="Browse" image="Browse" id="Uwh-Bg-Ymq"/>
|
<tabBarItem key="tabBarItem" title="Browse" image="Browse" id="Uwh-Bg-Ymq"/>
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="dIv-qd-9L5" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="dIv-qd-9L5" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<color key="tintColor" name="Primary"/>
|
<color key="tintColor" name="Primary"/>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
@@ -626,7 +626,7 @@ World</string>
|
|||||||
</tabBarItem>
|
</tabBarItem>
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="CzO-Kt-BlZ" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="CzO-Kt-BlZ" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
<nil name="viewControllers"/>
|
<nil name="viewControllers"/>
|
||||||
@@ -883,7 +883,7 @@ World</string>
|
|||||||
<navigationItem key="navigationItem" title="App IDs" id="3Co-uv-Fhb">
|
<navigationItem key="navigationItem" title="App IDs" id="3Co-uv-Fhb">
|
||||||
<barButtonItem key="leftBarButtonItem" style="plain" id="Aqs-QK-Ups">
|
<barButtonItem key="leftBarButtonItem" style="plain" id="Aqs-QK-Ups">
|
||||||
<view key="customView" contentMode="scaleToFill" id="p0q-Fg-3Ba">
|
<view key="customView" contentMode="scaleToFill" id="p0q-Fg-3Ba">
|
||||||
<rect key="frame" x="16" y="1" width="83" height="42"/>
|
<rect key="frame" x="16" y="7" width="83" height="42"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
</view>
|
</view>
|
||||||
</barButtonItem>
|
</barButtonItem>
|
||||||
@@ -909,7 +909,7 @@ World</string>
|
|||||||
<tabBarItem key="tabBarItem" title="News" image="News" id="fVN-ed-uO1"/>
|
<tabBarItem key="tabBarItem" title="News" image="News" id="fVN-ed-uO1"/>
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="525-jF-uDK" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="525-jF-uDK" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
@@ -928,7 +928,7 @@ World</string>
|
|||||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="IXk-qg-mFJ" sceneMemberID="viewController">
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="IXk-qg-mFJ" sceneMemberID="viewController">
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="9sB-f3-Fnk">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="9sB-f3-Fnk">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="108"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
<nil name="viewControllers"/>
|
<nil name="viewControllers"/>
|
||||||
@@ -1070,7 +1070,7 @@ World</string>
|
|||||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="Qo4-72-Hmr" sceneMemberID="viewController">
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="Qo4-72-Hmr" sceneMemberID="viewController">
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="mcx-oR-qPe">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="mcx-oR-qPe">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="108"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</navigationBar>
|
</navigationBar>
|
||||||
<nil name="viewControllers"/>
|
<nil name="viewControllers"/>
|
||||||
@@ -1095,13 +1095,13 @@ World</string>
|
|||||||
<image name="News" width="19" height="20"/>
|
<image name="News" width="19" height="20"/>
|
||||||
<image name="Settings" width="20" height="20"/>
|
<image name="Settings" width="20" height="20"/>
|
||||||
<namedColor name="Background">
|
<namedColor name="Background">
|
||||||
<color red="0.6431" green="0.0196" blue="0.9804" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.45098039215686275" green="0.015686274509803921" blue="0.68627450980392157" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
<namedColor name="BlurTint">
|
<namedColor name="BlurTint">
|
||||||
<color red="1" green="1" blue="1" alpha="0.3" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="1" green="1" blue="1" alpha="0.30000001192092896" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
<namedColor name="Primary">
|
<namedColor name="Primary">
|
||||||
<color red="0.6431" green="0.0196" blue="0.9804" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.64313725490196083" green="0.019607843137254902" blue="0.98039215686274506" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
<systemColor name="systemBackgroundColor">
|
<systemColor name="systemBackgroundColor">
|
||||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
|||||||
@@ -114,9 +114,9 @@ private extension BrowseViewController
|
|||||||
let progress = AppManager.shared.installationProgress(for: app)
|
let progress = AppManager.shared.installationProgress(for: app)
|
||||||
cell.bannerView.button.progress = progress
|
cell.bannerView.button.progress = progress
|
||||||
|
|
||||||
if let versionDate = app.latestVersion?.date, versionDate > Date()
|
if let versionDate = app.latestSupportedVersion?.date, versionDate > Date()
|
||||||
{
|
{
|
||||||
cell.bannerView.button.countdownDate = app.versionDate
|
cell.bannerView.button.countdownDate = versionDate
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -278,7 +278,7 @@ private extension BrowseViewController
|
|||||||
{
|
{
|
||||||
case .failure(OperationError.cancelled): break // Ignore
|
case .failure(OperationError.cancelled): break // Ignore
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
let toastView = ToastView(error: error)
|
let toastView = ToastView(error: error, opensLog: true)
|
||||||
toastView.show(in: self)
|
toastView.show(in: self)
|
||||||
|
|
||||||
case .success: print("Installed app:", app.bundleIdentifier)
|
case .success: print("Installed app:", app.bundleIdentifier)
|
||||||
|
|||||||
@@ -18,8 +18,17 @@ extension TimeInterval
|
|||||||
|
|
||||||
final class ToastView: RSTToastView
|
final class ToastView: RSTToastView
|
||||||
{
|
{
|
||||||
|
static let openErrorLogNotification = Notification.Name("ALTOpenErrorLogNotification")
|
||||||
|
|
||||||
var preferredDuration: TimeInterval
|
var preferredDuration: TimeInterval
|
||||||
|
|
||||||
|
var opensErrorLog: Bool = false
|
||||||
|
|
||||||
|
convenience init(text: String, detailText: String?, opensLog: Bool = false) {
|
||||||
|
self.init(text: text, detailText: detailText)
|
||||||
|
self.opensErrorLog = opensLog
|
||||||
|
}
|
||||||
|
|
||||||
override init(text: String, detailText detailedText: String?)
|
override init(text: String, detailText detailedText: String?)
|
||||||
{
|
{
|
||||||
if detailedText == nil
|
if detailedText == nil
|
||||||
@@ -43,53 +52,43 @@ final class ToastView: RSTToastView
|
|||||||
// RSTToastView does not expose stack view containing labels,
|
// RSTToastView does not expose stack view containing labels,
|
||||||
// so we access it indirectly as the labels' superview.
|
// so we access it indirectly as the labels' superview.
|
||||||
stackView.spacing = (detailedText != nil) ? 4.0 : 0.0
|
stackView.spacing = (detailedText != nil) ? 4.0 : 0.0
|
||||||
|
stackView.alignment = .leading
|
||||||
}
|
}
|
||||||
|
self.addTarget(self, action: #selector(ToastView.showErrorLog), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convenience init(error: Error, opensLog: Bool = false) {
|
||||||
|
self.init(error: error)
|
||||||
|
self.opensErrorLog = opensLog
|
||||||
|
}
|
||||||
|
|
||||||
convenience init(error: Error)
|
convenience init(error: Error)
|
||||||
{
|
{
|
||||||
var error = error as NSError
|
var error = error as NSError
|
||||||
var underlyingError = error.underlyingError
|
var underlyingError = error.underlyingError
|
||||||
|
|
||||||
var preferredDuration: TimeInterval?
|
|
||||||
|
|
||||||
if
|
if
|
||||||
let unwrappedUnderlyingError = underlyingError,
|
let unwrappedUnderlyingError = underlyingError,
|
||||||
error.domain == AltServerErrorDomain && error.code == ALTServerError.Code.underlyingError.rawValue
|
error.domain == AltServerErrorDomain && error.code == ALTServerError.Code.underlyingError.rawValue
|
||||||
{
|
{
|
||||||
// Treat underlyingError as the primary error.
|
// Treat underlyingError as the primary error, but keep localized title + failure.
|
||||||
|
let nsError = error as NSError
|
||||||
error = unwrappedUnderlyingError as NSError
|
error = unwrappedUnderlyingError as NSError
|
||||||
|
|
||||||
|
if let localizedTitle = nsError.localizedTitle {
|
||||||
|
error = error.withLocalizedTitle(localizedTitle)
|
||||||
|
}
|
||||||
|
if let localizedFailure = nsError.localizedFailure {
|
||||||
|
error = error.withLocalizedFailure(localizedFailure)
|
||||||
|
}
|
||||||
|
|
||||||
underlyingError = nil
|
underlyingError = nil
|
||||||
|
|
||||||
preferredDuration = .longToastViewDuration
|
|
||||||
}
|
}
|
||||||
|
let text = error.localizedTitle ?? NSLocalizedString("Operation Failed", comment: "")
|
||||||
let text: String
|
let detailText = error.localizedDescription
|
||||||
let detailText: String?
|
|
||||||
|
|
||||||
if let failure = error.localizedFailure
|
|
||||||
{
|
|
||||||
text = failure
|
|
||||||
detailText = error.localizedFailureReason ?? error.localizedRecoverySuggestion ?? underlyingError?.localizedDescription ?? error.localizedDescription
|
|
||||||
}
|
|
||||||
else if let reason = error.localizedFailureReason
|
|
||||||
{
|
|
||||||
text = reason
|
|
||||||
detailText = error.localizedRecoverySuggestion ?? underlyingError?.localizedDescription
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
text = error.localizedDescription
|
|
||||||
detailText = underlyingError?.localizedDescription ?? error.localizedRecoverySuggestion
|
|
||||||
}
|
|
||||||
|
|
||||||
self.init(text: text, detailText: detailText)
|
self.init(text: text, detailText: detailText)
|
||||||
|
|
||||||
if let preferredDuration = preferredDuration
|
|
||||||
{
|
|
||||||
self.preferredDuration = preferredDuration
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init(coder aDecoder: NSCoder) {
|
required init(coder aDecoder: NSCoder) {
|
||||||
@@ -112,6 +111,18 @@ final class ToastView: RSTToastView
|
|||||||
|
|
||||||
override func show(in view: UIView, duration: TimeInterval)
|
override func show(in view: UIView, duration: TimeInterval)
|
||||||
{
|
{
|
||||||
|
if opensErrorLog, #available(iOS 13.0, *), case let configuration = UIImage.SymbolConfiguration(font: self.textLabel.font),
|
||||||
|
let icon = UIImage(systemName: "chevron.right.circle", withConfiguration: configuration) {
|
||||||
|
let tintedIcon = icon.withTintColor(.white, renderingMode: .alwaysOriginal)
|
||||||
|
let moreIconImageView = UIImageView(image: tintedIcon)
|
||||||
|
moreIconImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
self.addSubview(moreIconImageView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
moreIconImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -self.layoutMargins.right),
|
||||||
|
moreIconImageView.centerYAnchor.constraint(equalTo: self.textLabel.centerYAnchor),
|
||||||
|
moreIconImageView.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: self.textLabel.trailingAnchor, multiplier: 1.0)
|
||||||
|
])
|
||||||
|
}
|
||||||
super.show(in: view, duration: duration)
|
super.show(in: view, duration: duration)
|
||||||
|
|
||||||
let announcement = (self.textLabel.text ?? "") + ". " + (self.detailTextLabel.text ?? "")
|
let announcement = (self.textLabel.text ?? "") + ". " + (self.detailTextLabel.text ?? "")
|
||||||
@@ -127,4 +138,10 @@ final class ToastView: RSTToastView
|
|||||||
{
|
{
|
||||||
self.show(in: view, duration: self.preferredDuration)
|
self.show(in: view, duration: self.preferredDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
func showErrorLog() {
|
||||||
|
guard self.opensErrorLog else { return }
|
||||||
|
NotificationCenter.default.post(name: ToastView.openErrorLogNotification, object: self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>ALTAnisetteURL</key>
|
||||||
|
<string>https://ani.sidestore.io</string>
|
||||||
<key>ALTAppGroups</key>
|
<key>ALTAppGroups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||||
@@ -9,12 +11,10 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>ALTDeviceID</key>
|
<key>ALTDeviceID</key>
|
||||||
<string>00008101-000129D63698001E</string>
|
<string>00008101-000129D63698001E</string>
|
||||||
<key>ALTServerID</key>
|
|
||||||
<string>1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D</string>
|
|
||||||
<key>ALTPairingFile</key>
|
<key>ALTPairingFile</key>
|
||||||
<string><insert pairing file here></string>
|
<string><insert pairing file here></string>
|
||||||
<key>ALTAnisetteURL</key>
|
<key>ALTServerID</key>
|
||||||
<string>https://ani.sidestore.io</string>
|
<string>1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D</string>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDocumentTypes</key>
|
<key>CFBundleDocumentTypes</key>
|
||||||
@@ -44,8 +44,6 @@
|
|||||||
<string>$(PRODUCT_NAME)</string>
|
<string>$(PRODUCT_NAME)</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
|
||||||
<true/>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>$(MARKETING_VERSION)</string>
|
<string>$(MARKETING_VERSION)</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
@@ -93,6 +91,13 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
<key>NSBonjourServices</key>
|
<key>NSBonjourServices</key>
|
||||||
<array>
|
<array>
|
||||||
<string>_altserver._tcp</string>
|
<string>_altserver._tcp</string>
|
||||||
@@ -131,13 +136,10 @@
|
|||||||
<string>fetch</string>
|
<string>fetch</string>
|
||||||
<string>remote-notification</string>
|
<string>remote-notification</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>UIFileSharingEnabled</key>
|
||||||
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>NSAppTransportSecurity</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
@@ -204,7 +206,5 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>UIFileSharingEnabled</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import minimuxer
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
|
|
||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
@@ -39,8 +40,12 @@ final class IntentHandler: NSObject, RefreshAllIntentHandling
|
|||||||
|
|
||||||
// Give ourselves 9 extra seconds before starting handle() timeout timer.
|
// Give ourselves 9 extra seconds before starting handle() timeout timer.
|
||||||
// 10 seconds or longer results in timeout regardless.
|
// 10 seconds or longer results in timeout regardless.
|
||||||
self.queue.asyncAfter(deadline: .now() + 9.0) {
|
self.queue.asyncAfter(deadline: .now() + 8.0) {
|
||||||
self.finish(intent, response: RefreshAllIntentResponse(code: .ready, userActivity: nil))
|
if minimuxer.ready() {
|
||||||
|
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
||||||
|
} else {
|
||||||
|
self.finish(intent, response: RefreshAllIntentResponse(code: .failure, userActivity: nil))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !DatabaseManager.shared.isStarted
|
if !DatabaseManager.shared.isStarted
|
||||||
@@ -52,12 +57,14 @@ final class IntentHandler: NSObject, RefreshAllIntentHandling
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
self.finish(intent, response: RefreshAllIntentResponse(code: .ready, userActivity: nil))
|
||||||
self.refreshApps(intent: intent)
|
self.refreshApps(intent: intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
self.finish(intent, response: RefreshAllIntentResponse(code: .ready, userActivity: nil))
|
||||||
self.refreshApps(intent: intent)
|
self.refreshApps(intent: intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,6 +90,11 @@ final class IntentHandler: NSObject, RefreshAllIntentHandling
|
|||||||
// We took too long to finish and return the final result,
|
// We took too long to finish and return the final result,
|
||||||
// so we'll now present a normal notification when finished.
|
// so we'll now present a normal notification when finished.
|
||||||
operation.presentsFinishedNotification = true
|
operation.presentsFinishedNotification = true
|
||||||
|
if minimuxer.ready() {
|
||||||
|
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
||||||
|
} else {
|
||||||
|
self.finish(intent, response: RefreshAllIntentResponse(code: .failure, userActivity: nil))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.finish(intent, response: RefreshAllIntentResponse(code: .inProgress, userActivity: nil))
|
self.finish(intent, response: RefreshAllIntentResponse(code: .inProgress, userActivity: nil))
|
||||||
@@ -106,6 +118,8 @@ private extension IntentHandler
|
|||||||
{
|
{
|
||||||
// Queue response in case refreshing finishes after confirm() but before handle().
|
// Queue response in case refreshing finishes after confirm() but before handle().
|
||||||
self.queuedResponses[intent] = response
|
self.queuedResponses[intent] = response
|
||||||
|
|
||||||
|
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,10 +140,12 @@ private extension IntentHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
||||||
|
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||||
}
|
}
|
||||||
catch RefreshError.noInstalledApps
|
catch ~RefreshErrorCode.noInstalledApps
|
||||||
{
|
{
|
||||||
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
self.finish(intent, response: RefreshAllIntentResponse(code: .success, userActivity: nil))
|
||||||
|
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||||
}
|
}
|
||||||
catch let error as NSError
|
catch let error as NSError
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,6 +49,43 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
|||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(true)
|
super.viewDidAppear(true)
|
||||||
|
if #available(iOS 17, *), !UserDefaults.standard.sidejitenable {
|
||||||
|
DispatchQueue.global().async {
|
||||||
|
self.isSideJITServerDetected() { result in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
switch result {
|
||||||
|
case .success():
|
||||||
|
let dialogMessage = UIAlertController(title: "SideJITServer Detected", message: "Would you like to enable SideJITServer", preferredStyle: .alert)
|
||||||
|
|
||||||
|
// Create OK button with action handler
|
||||||
|
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
||||||
|
UserDefaults.standard.sidejitenable = true
|
||||||
|
})
|
||||||
|
|
||||||
|
let cancel = UIAlertAction(title: "Cancel", style: .cancel)
|
||||||
|
//Add OK button to a dialog message
|
||||||
|
dialogMessage.addAction(ok)
|
||||||
|
dialogMessage.addAction(cancel)
|
||||||
|
|
||||||
|
// Present Alert to
|
||||||
|
self.present(dialogMessage, animated: true, completion: nil)
|
||||||
|
case .failure(_):
|
||||||
|
print("Cannot find sideJITServer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if #available(iOS 17, *), UserDefaults.standard.sidejitenable {
|
||||||
|
DispatchQueue.global().async {
|
||||||
|
self.askfornetwork()
|
||||||
|
}
|
||||||
|
print("SideJITServer Enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#if !targetEnvironment(simulator)
|
#if !targetEnvironment(simulator)
|
||||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||||
|
|
||||||
@@ -60,6 +97,46 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func askfornetwork() {
|
||||||
|
let address = UserDefaults.standard.textInputSideJITServerurl ?? ""
|
||||||
|
|
||||||
|
var SJSURL = address
|
||||||
|
|
||||||
|
if (UserDefaults.standard.textInputSideJITServerurl ?? "").isEmpty {
|
||||||
|
SJSURL = "http://sidejitserver._http._tcp.local:8080"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a network operation at launch to Refresh SideJITServer
|
||||||
|
let url = URL(string: "\(SJSURL)/re/")!
|
||||||
|
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
|
||||||
|
print(data)
|
||||||
|
}
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSideJITServerDetected(completion: @escaping (Result<Void, Error>) -> Void) {
|
||||||
|
let address = UserDefaults.standard.textInputSideJITServerurl ?? ""
|
||||||
|
|
||||||
|
var SJSURL = address
|
||||||
|
|
||||||
|
if (UserDefaults.standard.textInputSideJITServerurl ?? "").isEmpty {
|
||||||
|
SJSURL = "http://sidejitserver._http._tcp.local:8080"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a network operation at launch to Refresh SideJITServer
|
||||||
|
let url = URL(string: SJSURL)!
|
||||||
|
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
|
||||||
|
if let error = error {
|
||||||
|
print("No SideJITServer on Network")
|
||||||
|
completion(.failure(error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
task.resume()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func fetchPairingFile() -> String? {
|
func fetchPairingFile() -> String? {
|
||||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||||
let fm = FileManager.default
|
let fm = FileManager.default
|
||||||
@@ -72,16 +149,17 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
|||||||
fm.fileExists(atPath: appResourcePath.path),
|
fm.fileExists(atPath: appResourcePath.path),
|
||||||
let data = fm.contents(atPath: appResourcePath.path),
|
let data = fm.contents(atPath: appResourcePath.path),
|
||||||
let contents = String(data: data, encoding: .utf8),
|
let contents = String(data: data, encoding: .utf8),
|
||||||
!contents.isEmpty {
|
!contents.isEmpty,
|
||||||
|
!UserDefaults.standard.isPairingReset {
|
||||||
print("Loaded ALTPairingFile from \(appResourcePath.path)")
|
print("Loaded ALTPairingFile from \(appResourcePath.path)")
|
||||||
return contents
|
return contents
|
||||||
} else if let plistString = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String, !plistString.isEmpty, !plistString.contains("insert pairing file here"){
|
} else if let plistString = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String, !plistString.isEmpty, !plistString.contains("insert pairing file here"), !UserDefaults.standard.isPairingReset{
|
||||||
print("Loaded ALTPairingFile from Info.plist")
|
print("Loaded ALTPairingFile from Info.plist")
|
||||||
return plistString
|
return plistString
|
||||||
} else {
|
} else {
|
||||||
// Show an alert explaining the pairing file
|
// Show an alert explaining the pairing file
|
||||||
// Create new Alert
|
// Create new Alert
|
||||||
let dialogMessage = UIAlertController(title: "Pairing File", message: "Select the pairing file for your device. For more information, go to https://wiki.sidestore.io/guides/install#pairing-process", preferredStyle: .alert)
|
let dialogMessage = UIAlertController(title: "Pairing File", message: "Select the pairing file or select \"Help\" for help.", preferredStyle: .alert)
|
||||||
|
|
||||||
// Create OK button with action handler
|
// Create OK button with action handler
|
||||||
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
||||||
@@ -93,14 +171,33 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
|||||||
documentPickerController.shouldShowFileExtensions = true
|
documentPickerController.shouldShowFileExtensions = true
|
||||||
documentPickerController.delegate = self
|
documentPickerController.delegate = self
|
||||||
self.present(documentPickerController, animated: true, completion: nil)
|
self.present(documentPickerController, animated: true, completion: nil)
|
||||||
|
UserDefaults.standard.isPairingReset = false
|
||||||
})
|
})
|
||||||
|
|
||||||
//Add OK button to a dialog message
|
//Add "help" button to take user to wiki
|
||||||
|
let wikiOption = UIAlertAction(title: "Help", style: .default) { (action) in
|
||||||
|
let wikiURL: String = "https://docs.sidestore.io/docs/getting-started/pairing-file"
|
||||||
|
if let url = URL(string: wikiURL) {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
sleep(2)
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add buttons to dialog message
|
||||||
|
dialogMessage.addAction(wikiOption)
|
||||||
dialogMessage.addAction(ok)
|
dialogMessage.addAction(ok)
|
||||||
|
|
||||||
// Present Alert to
|
// Present Alert to
|
||||||
self.present(dialogMessage, animated: true, completion: nil)
|
self.present(dialogMessage, animated: true, completion: nil)
|
||||||
|
|
||||||
|
let dialogMessage2 = UIAlertController(title: "Analytics", message: "This app contains anonymous analytics for research and project development. By continuing to use this app, you are consenting to this data collection", preferredStyle: .alert)
|
||||||
|
|
||||||
|
let ok2 = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in})
|
||||||
|
|
||||||
|
dialogMessage2.addAction(ok2)
|
||||||
|
self.present(dialogMessage2, animated: true, completion: nil)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
81
AltStore/Managing Apps/AppExtensionView.swift
Normal file
81
AltStore/Managing Apps/AppExtensionView.swift
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
//
|
||||||
|
// AppExtensionView.swift
|
||||||
|
// SideStore
|
||||||
|
//
|
||||||
|
// Created by June P on 8/17/24.
|
||||||
|
// Copyright © 2024 SideStore. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import CAltSign
|
||||||
|
|
||||||
|
extension ALTApplication: Identifiable {}
|
||||||
|
|
||||||
|
struct AppExtensionView: View {
|
||||||
|
var extensions: Set<ALTApplication>
|
||||||
|
@State var selection: [ALTApplication] = []
|
||||||
|
|
||||||
|
var completion: (_ selection: [ALTApplication]) -> Any?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
List {
|
||||||
|
ForEach(self.extensions.sorted {
|
||||||
|
$0.bundleIdentifier < $1.bundleIdentifier
|
||||||
|
}, id: \.self) { item in
|
||||||
|
MultipleSelectionRow(title: item.bundleIdentifier, isSelected: !selection.contains(item)) {
|
||||||
|
if self.selection.contains(item) {
|
||||||
|
self.selection.removeAll(where: { $0 == item })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.selection.append(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("App Extensions")
|
||||||
|
.onDisappear {
|
||||||
|
_ = completion(selection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MultipleSelectionRow: View {
|
||||||
|
var title: String
|
||||||
|
var isSelected: Bool
|
||||||
|
var action: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
SwiftUI.Button(action: self.action) {
|
||||||
|
HStack {
|
||||||
|
Text(self.title)
|
||||||
|
if self.isSelected {
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "checkmark")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppExtensionViewHostingController: UIHostingController<AppExtensionView> {
|
||||||
|
|
||||||
|
|
||||||
|
var completion: Optional<(_ selection: [ALTApplication]) -> Any?> = nil
|
||||||
|
|
||||||
|
required init(extensions: Set<ALTApplication>, completion: @escaping (_ selection: [ALTApplication]) -> Any?) {
|
||||||
|
self.completion = completion
|
||||||
|
super.init(rootView: AppExtensionView(extensions: extensions, completion: completion))
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required dynamic init?(coder aDecoder: NSCoder) {
|
||||||
|
super.init(coder: aDecoder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppExtensionViewHostingController: UIPopoverPresentationControllerDelegate {
|
||||||
|
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,12 +8,14 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
import Intents
|
import Intents
|
||||||
import Combine
|
import Combine
|
||||||
import WidgetKit
|
import WidgetKit
|
||||||
|
|
||||||
|
import minimuxer
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
import Roxas
|
||||||
@@ -37,17 +39,12 @@ final class AppManagerPublisher: ObservableObject
|
|||||||
fileprivate(set) var refreshProgress = [String: Progress]()
|
fileprivate(set) var refreshProgress = [String: Progress]()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func ==(lhs: OperatingSystemVersion, rhs: OperatingSystemVersion) -> Bool
|
|
||||||
{
|
|
||||||
return (lhs.majorVersion == rhs.majorVersion && lhs.minorVersion == rhs.minorVersion && lhs.patchVersion == rhs.patchVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
final class AppManager
|
final class AppManager
|
||||||
{
|
{
|
||||||
static let shared = AppManager()
|
static let shared = AppManager()
|
||||||
|
|
||||||
private(set) var updatePatronsResult: Result<Void, Error>?
|
private(set) var updatePatronsResult: Result<Void, Error>?
|
||||||
|
|
||||||
private let operationQueue = OperationQueue()
|
private let operationQueue = OperationQueue()
|
||||||
private let serialOperationQueue = OperationQueue()
|
private let serialOperationQueue = OperationQueue()
|
||||||
|
|
||||||
@@ -307,6 +304,45 @@ extension AppManager
|
|||||||
presentingViewController.present(alertController, animated: true, completion: nil)
|
presentingViewController.present(alertController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clearAppCache(completion: @escaping (Result<Void, Error>) -> Void)
|
||||||
|
{
|
||||||
|
let clearAppCacheOperation = ClearAppCacheOperation()
|
||||||
|
clearAppCacheOperation.resultHandler = { result in
|
||||||
|
completion(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.run([clearAppCacheOperation], context: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func log(_ error: Error, operation: LoggedError.Operation, app: AppProtocol)
|
||||||
|
{
|
||||||
|
switch error {
|
||||||
|
case ~OperationError.Code.cancelled: return // Don't log cancelled events
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
// Sanitize NSError on same thread before performing background task.
|
||||||
|
let sanitizedError = (error as NSError).sanitizedForSerialization()
|
||||||
|
|
||||||
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||||
|
var app = app
|
||||||
|
if let managedApp = app as? NSManagedObject, let tempApp = context.object(with: managedApp.objectID) as? AppProtocol
|
||||||
|
{
|
||||||
|
app = tempApp
|
||||||
|
}
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
_ = LoggedError(error: sanitizedError, app: app, operation: operation, context: context)
|
||||||
|
try context.save()
|
||||||
|
}
|
||||||
|
catch let saveError
|
||||||
|
{
|
||||||
|
print("[ALTLog] Failed to log error \(sanitizedError.domain) code \(sanitizedError.code) for \(app.bundleIdentifier):", saveError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppManager
|
extension AppManager
|
||||||
@@ -359,7 +395,7 @@ extension AppManager
|
|||||||
case .success(let source): fetchedSources.insert(source)
|
case .success(let source): fetchedSources.insert(source)
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
let source = managedObjectContext.object(with: source.objectID) as! Source
|
let source = managedObjectContext.object(with: source.objectID) as! Source
|
||||||
source.error = (error as NSError).sanitizedForCoreData()
|
source.error = (error as NSError).sanitizedForSerialization()
|
||||||
errors[source] = error
|
errors[source] = error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,7 +483,7 @@ extension AppManager
|
|||||||
group.completionHandler = { (results) in
|
group.completionHandler = { (results) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let result = results.values.first else { throw context.error ?? OperationError.unknown }
|
guard let result = results.values.first else { throw context.error ?? OperationError.unknown() }
|
||||||
completionHandler(result)
|
completionHandler(result)
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -466,7 +502,7 @@ extension AppManager
|
|||||||
func update(_ app: InstalledApp, presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
|
func update(_ app: InstalledApp, presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
|
||||||
{
|
{
|
||||||
guard let storeApp = app.storeApp else {
|
guard let storeApp = app.storeApp else {
|
||||||
completionHandler(.failure(OperationError.appNotFound))
|
completionHandler(.failure(OperationError.appNotFound(name: app.name)))
|
||||||
return Progress.discreteProgress(totalUnitCount: 1)
|
return Progress.discreteProgress(totalUnitCount: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,7 +510,7 @@ extension AppManager
|
|||||||
group.completionHandler = { (results) in
|
group.completionHandler = { (results) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let result = results.values.first else { throw OperationError.unknown }
|
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||||
completionHandler(result)
|
completionHandler(result)
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -510,8 +546,8 @@ extension AppManager
|
|||||||
group.completionHandler = { (results) in
|
group.completionHandler = { (results) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let result = results.values.first else { throw OperationError.unknown }
|
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||||
|
|
||||||
let installedApp = try result.get()
|
let installedApp = try result.get()
|
||||||
assert(installedApp.managedObjectContext != nil)
|
assert(installedApp.managedObjectContext != nil)
|
||||||
|
|
||||||
@@ -549,7 +585,7 @@ extension AppManager
|
|||||||
group.completionHandler = { (results) in
|
group.completionHandler = { (results) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let result = results.values.first else { throw OperationError.unknown }
|
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||||
|
|
||||||
let installedApp = try result.get()
|
let installedApp = try result.get()
|
||||||
assert(installedApp.managedObjectContext != nil)
|
assert(installedApp.managedObjectContext != nil)
|
||||||
@@ -575,8 +611,8 @@ extension AppManager
|
|||||||
group.completionHandler = { (results) in
|
group.completionHandler = { (results) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let result = results.values.first else { throw OperationError.unknown }
|
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||||
|
|
||||||
let installedApp = try result.get()
|
let installedApp = try result.get()
|
||||||
assert(installedApp.managedObjectContext != nil)
|
assert(installedApp.managedObjectContext != nil)
|
||||||
|
|
||||||
@@ -600,7 +636,7 @@ extension AppManager
|
|||||||
group.completionHandler = { (results) in
|
group.completionHandler = { (results) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let result = results.values.first else { throw OperationError.unknown }
|
guard let result = results.values.first else { throw OperationError.unknown() }
|
||||||
|
|
||||||
let installedApp = try result.get()
|
let installedApp = try result.get()
|
||||||
assert(installedApp.managedObjectContext != nil)
|
assert(installedApp.managedObjectContext != nil)
|
||||||
@@ -670,13 +706,20 @@ extension AppManager
|
|||||||
var installedApp: InstalledApp?
|
var installedApp: InstalledApp?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let appName = installedApp.name
|
||||||
let context = Context()
|
let context = Context()
|
||||||
context.installedApp = installedApp
|
context.installedApp = installedApp
|
||||||
|
|
||||||
|
|
||||||
let enableJITOperation = EnableJITOperation(context: context)
|
let enableJITOperation = EnableJITOperation(context: context)
|
||||||
enableJITOperation.resultHandler = { (result) in
|
enableJITOperation.resultHandler = { (result) in
|
||||||
completionHandler(result)
|
switch result {
|
||||||
|
case .success: completionHandler(.success(()))
|
||||||
|
case .failure(let nsError as NSError):
|
||||||
|
let localizedTitle = String(format: NSLocalizedString("Failed to enable JIT for %@", comment: ""), appName)
|
||||||
|
let error = nsError.withLocalizedTitle(localizedTitle)
|
||||||
|
self.log(error, operation: .enableJIT, app: installedApp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.run([enableJITOperation], context: context, requiresSerialQueue: true)
|
self.run([enableJITOperation], context: context, requiresSerialQueue: true)
|
||||||
@@ -754,6 +797,12 @@ extension AppManager
|
|||||||
let progress = self.refreshProgress[app.bundleIdentifier]
|
let progress = self.refreshProgress[app.bundleIdentifier]
|
||||||
return progress
|
return progress
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isActivelyManagingApp(withBundleID bundleID: String) -> Bool
|
||||||
|
{
|
||||||
|
let isActivelyManaging = self.installationProgress.keys.contains(bundleID) || self.refreshProgress.keys.contains(bundleID)
|
||||||
|
return isActivelyManaging
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppManager
|
extension AppManager
|
||||||
@@ -806,12 +855,18 @@ private extension AppManager
|
|||||||
|
|
||||||
return bundleIdentifier
|
return bundleIdentifier
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
var loggedErrorOperation: LoggedError.Operation {
|
||||||
func isActivelyManagingApp(withBundleID bundleID: String) -> Bool
|
switch self {
|
||||||
{
|
case .install: return .install
|
||||||
let isActivelyManaging = self.installationProgress.keys.contains(bundleID) || self.refreshProgress.keys.contains(bundleID)
|
case .update: return .update
|
||||||
return isActivelyManaging
|
case .refresh: return .refresh
|
||||||
|
case .activate: return .activate
|
||||||
|
case .deactivate: return .deactivate
|
||||||
|
case .backup: return .backup
|
||||||
|
case .restore: return .restore
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
@@ -948,12 +1003,100 @@ private extension AppManager
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
DispatchQueue.main.schedule {
|
||||||
|
UIApplication.shared.isIdleTimerDisabled = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||||
|
}
|
||||||
performAppOperations()
|
performAppOperations()
|
||||||
|
DispatchQueue.main.schedule {
|
||||||
|
UIApplication.shared.isIdleTimerDisabled = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return group
|
return group
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeAppExtensions(from application: ALTApplication, extensions: Set<ALTApplication>, _ presentingViewController: UIViewController, completion: @escaping (Result<Void, Error>) -> Void)
|
||||||
|
{
|
||||||
|
guard !application.appExtensions.isEmpty else { return completion(.success(())) }
|
||||||
|
|
||||||
|
let firstSentence: String
|
||||||
|
|
||||||
|
if UserDefaults.standard.activeAppLimitIncludesExtensions
|
||||||
|
{
|
||||||
|
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions.", comment: "")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to creating 10 App IDs per week.", comment: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit? There are \(extensions.count) Extensions", comment: "")
|
||||||
|
|
||||||
|
let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert)
|
||||||
|
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
|
||||||
|
completion(.failure(OperationError.cancelled))
|
||||||
|
}))
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in
|
||||||
|
completion(.success(()))
|
||||||
|
})
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
for appExtension in application.appExtensions
|
||||||
|
{
|
||||||
|
try FileManager.default.removeItem(at: appExtension.fileURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Choose App Extensions", comment: ""), style: .default) { (action) in
|
||||||
|
let popoverContentController = AppExtensionViewHostingController(extensions: extensions) { (selection) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
for appExtension in selection
|
||||||
|
{
|
||||||
|
print("Deleting extension \(appExtension.bundleIdentifier)")
|
||||||
|
|
||||||
|
try FileManager.default.removeItem(at: appExtension.fileURL)
|
||||||
|
}
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let suiview = popoverContentController.view!
|
||||||
|
suiview.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
|
||||||
|
popoverContentController.modalPresentationStyle = .popover
|
||||||
|
|
||||||
|
if let popoverPresentationController = popoverContentController.popoverPresentationController {
|
||||||
|
popoverPresentationController.sourceView = presentingViewController.view
|
||||||
|
popoverPresentationController.sourceRect = CGRect(x: 50, y: 50, width: 4, height: 4)
|
||||||
|
popoverPresentationController.delegate = popoverContentController
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
presentingViewController.present(popoverContentController, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
presentingViewController.present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func _install(_ app: AppProtocol, operation appOperation: AppOperation, group: RefreshGroup, context: InstallAppOperationContext? = nil, additionalEntitlements: [ALTEntitlement: Any]? = nil, cacheApp: Bool = true, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
|
private func _install(_ app: AppProtocol, operation appOperation: AppOperation, group: RefreshGroup, context: InstallAppOperationContext? = nil, additionalEntitlements: [ALTEntitlement: Any]? = nil, cacheApp: Bool = true, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
|
||||||
{
|
{
|
||||||
let progress = Progress.discreteProgress(totalUnitCount: 100)
|
let progress = Progress.discreteProgress(totalUnitCount: 100)
|
||||||
@@ -1026,6 +1169,44 @@ private extension AppManager
|
|||||||
}
|
}
|
||||||
verifyOperation.addDependency(downloadOperation)
|
verifyOperation.addDependency(downloadOperation)
|
||||||
|
|
||||||
|
/* Remove App Extensions */
|
||||||
|
|
||||||
|
let removeAppExtensionsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if let error = context.error
|
||||||
|
{
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
guard case .install = appOperation else {
|
||||||
|
operation.finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let extensions = context.app?.appExtensions else { throw OperationError.invalidParameters }
|
||||||
|
|
||||||
|
guard let app = context.app, let presentingViewController = context.authenticatedContext.presentingViewController else { throw OperationError.invalidParameters }
|
||||||
|
|
||||||
|
|
||||||
|
self?.removeAppExtensions(from: app, extensions: extensions, presentingViewController) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(): break
|
||||||
|
case .failure(let error): context.error = error
|
||||||
|
}
|
||||||
|
operation.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
group.context.error = error
|
||||||
|
operation.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAppExtensionsOperation.addDependency(verifyOperation)
|
||||||
|
|
||||||
|
|
||||||
/* Refresh Anisette Data */
|
/* Refresh Anisette Data */
|
||||||
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
|
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
|
||||||
@@ -1036,7 +1217,7 @@ private extension AppManager
|
|||||||
case .success(let anisetteData): group.context.session?.anisetteData = anisetteData
|
case .success(let anisetteData): group.context.session?.anisetteData = anisetteData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
refreshAnisetteDataOperation.addDependency(verifyOperation)
|
refreshAnisetteDataOperation.addDependency(removeAppExtensionsOperation)
|
||||||
|
|
||||||
|
|
||||||
/* Fetch Provisioning Profiles */
|
/* Fetch Provisioning Profiles */
|
||||||
@@ -1046,7 +1227,9 @@ private extension AppManager
|
|||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): context.error = error
|
case .failure(let error): context.error = error
|
||||||
case .success(let provisioningProfiles): context.provisioningProfiles = provisioningProfiles
|
case .success(let provisioningProfiles):
|
||||||
|
context.provisioningProfiles = provisioningProfiles
|
||||||
|
print("PROVISIONING PROFILES \(context.provisioningProfiles)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchProvisioningProfilesOperation.addDependency(refreshAnisetteDataOperation)
|
fetchProvisioningProfilesOperation.addDependency(refreshAnisetteDataOperation)
|
||||||
@@ -1095,7 +1278,6 @@ private extension AppManager
|
|||||||
}
|
}
|
||||||
deactivateAppsOperation.addDependency(fetchProvisioningProfilesOperation)
|
deactivateAppsOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||||
|
|
||||||
|
|
||||||
/* Patch App */
|
/* Patch App */
|
||||||
let patchAppOperation = RSTAsyncBlockOperation { operation in
|
let patchAppOperation = RSTAsyncBlockOperation { operation in
|
||||||
do
|
do
|
||||||
@@ -1220,7 +1402,57 @@ private extension AppManager
|
|||||||
progress.addChild(installOperation.progress, withPendingUnitCount: 30)
|
progress.addChild(installOperation.progress, withPendingUnitCount: 30)
|
||||||
installOperation.addDependency(sendAppOperation)
|
installOperation.addDependency(sendAppOperation)
|
||||||
|
|
||||||
let operations = [downloadOperation, verifyOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, deactivateAppsOperation, patchAppOperation, resignAppOperation, sendAppOperation, installOperation]
|
let notificationRegistrationOperation = RSTAsyncBlockOperation { (operation) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
|
||||||
|
if let error = context.error
|
||||||
|
{
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
guard let app = context.installedApp else { operation.finish(); return }
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
|
content.title = "App Expiring Soon"
|
||||||
|
let formatter = DateComponentsFormatter()
|
||||||
|
formatter.unitsStyle = .full
|
||||||
|
formatter.includesApproximationPhrase = false
|
||||||
|
formatter.includesTimeRemainingPhrase = false
|
||||||
|
|
||||||
|
formatter.allowedUnits = [.day, .hour, .minute]
|
||||||
|
|
||||||
|
formatter.maximumUnitCount = 1
|
||||||
|
|
||||||
|
let scheduledDate = DateInterval(start: Date(), duration: 60 * 60 * 24 * 6)
|
||||||
|
|
||||||
|
|
||||||
|
guard let timeLeft = formatter.string(from: scheduledDate.end, to: app.expirationDate) else { operation.finish(); return }
|
||||||
|
|
||||||
|
content.body = "App \(app.name) is expiring in \(timeLeft). Open SideStore to refresh now"
|
||||||
|
|
||||||
|
var dateComponents = DateComponents()
|
||||||
|
dateComponents.calendar = Calendar.current
|
||||||
|
|
||||||
|
let trigger = UNCalendarNotificationTrigger(
|
||||||
|
dateMatching: dateComponents, repeats: true)
|
||||||
|
|
||||||
|
let request = UNNotificationRequest(identifier: app.bundleIdentifier, content: content, trigger: trigger)
|
||||||
|
|
||||||
|
let notificationCenter = UNUserNotificationCenter.current()
|
||||||
|
|
||||||
|
notificationCenter.add(request) {_ in
|
||||||
|
operation.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
operation.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationRegistrationOperation.addDependency(installOperation)
|
||||||
|
|
||||||
|
|
||||||
|
let operations = [downloadOperation, verifyOperation, removeAppExtensionsOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, deactivateAppsOperation, patchAppOperation, resignAppOperation, sendAppOperation, installOperation, notificationRegistrationOperation]
|
||||||
group.add(operations)
|
group.add(operations)
|
||||||
self.run(operations, context: group.context)
|
self.run(operations, context: group.context)
|
||||||
|
|
||||||
@@ -1253,14 +1485,21 @@ private extension AppManager
|
|||||||
case .success(let installedApp):
|
case .success(let installedApp):
|
||||||
completionHandler(.success(installedApp))
|
completionHandler(.success(installedApp))
|
||||||
|
|
||||||
case .failure(ALTServerError.unknownRequest), .failure(OperationError.appNotFound):
|
case .failure(MinimuxerError.ProfileInstall):
|
||||||
|
completionHandler(.failure(OperationError.noWiFi))
|
||||||
|
|
||||||
|
case .failure(ALTServerError.unknownRequest), .failure(OperationError.appNotFound(name: app.name)):
|
||||||
// Fall back to installation if AltServer doesn't support newer provisioning profile requests,
|
// Fall back to installation if AltServer doesn't support newer provisioning profile requests,
|
||||||
// OR if the cached app could not be found and we may need to redownload it.
|
// OR if the cached app could not be found and we may need to redownload it.
|
||||||
app.managedObjectContext?.performAndWait { // Must performAndWait to ensure we add operations before we return.
|
app.managedObjectContext?.performAndWait { // Must performAndWait to ensure we add operations before we return.
|
||||||
let installProgress = self._install(app, operation: operation, group: group) { (result) in
|
if minimuxer.ready() {
|
||||||
completionHandler(result)
|
let installProgress = self._install(app, operation: operation, group: group) { (result) in
|
||||||
|
completionHandler(result)
|
||||||
|
}
|
||||||
|
progress.addChild(installProgress, withPendingUnitCount: 40)
|
||||||
|
} else {
|
||||||
|
completionHandler(.failure(OperationError.noWiFi))
|
||||||
}
|
}
|
||||||
progress.addChild(installProgress, withPendingUnitCount: 40)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
@@ -1527,7 +1766,7 @@ private extension AppManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
guard let application = ALTApplication(fileURL: app.fileURL) else {
|
guard let application = ALTApplication(fileURL: app.fileURL) else {
|
||||||
completionHandler(.failure(OperationError.appNotFound))
|
completionHandler(.failure(OperationError.appNotFound(name: app.name)))
|
||||||
return progress
|
return progress
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1539,8 +1778,8 @@ private extension AppManager
|
|||||||
let temporaryDirectoryURL = context.temporaryDirectory.appendingPathComponent("AltBackup-" + UUID().uuidString)
|
let temporaryDirectoryURL = context.temporaryDirectory.appendingPathComponent("AltBackup-" + UUID().uuidString)
|
||||||
try FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
try FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
||||||
|
|
||||||
guard let altbackupFileURL = Bundle.main.url(forResource: "AltBackup", withExtension: "ipa") else { throw OperationError.appNotFound }
|
guard let altbackupFileURL = Bundle.main.url(forResource: "AltBackup", withExtension: "ipa") else { throw OperationError.appNotFound(name: app.name) }
|
||||||
|
|
||||||
let unzippedAppBundleURL = try FileManager.default.unzipAppBundle(at: altbackupFileURL, toDirectory: temporaryDirectoryURL)
|
let unzippedAppBundleURL = try FileManager.default.unzipAppBundle(at: altbackupFileURL, toDirectory: temporaryDirectoryURL)
|
||||||
guard let unzippedAppBundle = Bundle(url: unzippedAppBundleURL) else { throw OperationError.invalidApp }
|
guard let unzippedAppBundle = Bundle(url: unzippedAppBundleURL) else { throw OperationError.invalidApp }
|
||||||
|
|
||||||
@@ -1678,11 +1917,35 @@ private extension AppManager
|
|||||||
do { try installedApp.managedObjectContext?.save() }
|
do { try installedApp.managedObjectContext?.save() }
|
||||||
catch { print("Error saving installed app.", error) }
|
catch { print("Error saving installed app.", error) }
|
||||||
}
|
}
|
||||||
catch
|
catch let nsError as NSError
|
||||||
{
|
{
|
||||||
|
var appName: String!
|
||||||
|
if let app = operation.app as? (NSManagedObject & AppProtocol) {
|
||||||
|
if let context = app.managedObjectContext {
|
||||||
|
context.performAndWait {
|
||||||
|
appName = app.name
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
appName = NSLocalizedString("Unknown App", comment: "")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
appName = operation.app.name
|
||||||
|
}
|
||||||
|
|
||||||
|
let localizedTitle: String
|
||||||
|
switch operation {
|
||||||
|
case .install: localizedTitle = String(format: NSLocalizedString("Failed to Install %@", comment: ""), appName)
|
||||||
|
case .refresh: localizedTitle = String(format: NSLocalizedString("Failed to Refresh %@", comment: ""), appName)
|
||||||
|
case .update: localizedTitle = String(format: NSLocalizedString("Failed to Update %@", comment: ""), appName)
|
||||||
|
case .activate: localizedTitle = String(format: NSLocalizedString("Failed to Activate %@", comment: ""), appName)
|
||||||
|
case .deactivate: localizedTitle = String(format: NSLocalizedString("Failed to Deactivate %@", comment: ""), appName)
|
||||||
|
case .backup: localizedTitle = String(format: NSLocalizedString("Failed to Backup %@", comment: ""), appName)
|
||||||
|
case .restore: localizedTitle = String(format: NSLocalizedString("Failed to Restore %@ Backup", comment: ""), appName)
|
||||||
|
}
|
||||||
|
let error = nsError.withLocalizedTitle(localizedTitle)
|
||||||
group.set(.failure(error), forAppWithBundleIdentifier: operation.bundleIdentifier)
|
group.set(.failure(error), forAppWithBundleIdentifier: operation.bundleIdentifier)
|
||||||
|
|
||||||
self.log(error, for: operation)
|
self.log(error, operation: operation.loggedErrorOperation, app: operation.app)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1699,51 +1962,15 @@ private extension AppManager
|
|||||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeIntervalUntilNotification, repeats: false)
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeIntervalUntilNotification, repeats: false)
|
||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
content.title = NSLocalizedString("AltStore Expiring Soon", comment: "")
|
content.title = NSLocalizedString("SideStore Expiring Soon", comment: "")
|
||||||
content.body = NSLocalizedString("AltStore will expire in 24 hours. Open the app and refresh it to prevent it from expiring.", comment: "")
|
content.body = NSLocalizedString("SideStore will expire in 24 hours. Open the app and refresh it to prevent it from expiring.", comment: "")
|
||||||
content.sound = .default
|
content.sound = .default
|
||||||
|
|
||||||
let request = UNNotificationRequest(identifier: AppManager.expirationWarningNotificationID, content: content, trigger: trigger)
|
let request = UNNotificationRequest(identifier: AppManager.expirationWarningNotificationID, content: content, trigger: trigger)
|
||||||
UNUserNotificationCenter.current().add(request)
|
UNUserNotificationCenter.current().add(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func log(_ error: Error, for operation: AppOperation)
|
|
||||||
{
|
|
||||||
// Sanitize NSError on same thread before performing background task.
|
|
||||||
let sanitizedError = (error as NSError).sanitizedForCoreData()
|
|
||||||
|
|
||||||
let loggedErrorOperation: LoggedError.Operation = {
|
|
||||||
switch operation
|
|
||||||
{
|
|
||||||
case .install: return .install
|
|
||||||
case .update: return .update
|
|
||||||
case .refresh: return .refresh
|
|
||||||
case .activate: return .activate
|
|
||||||
case .deactivate: return .deactivate
|
|
||||||
case .backup: return .backup
|
|
||||||
case .restore: return .restore
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
|
||||||
var app = operation.app
|
|
||||||
if let managedApp = app as? NSManagedObject, let tempApp = context.object(with: managedApp.objectID) as? AppProtocol
|
|
||||||
{
|
|
||||||
app = tempApp
|
|
||||||
}
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
_ = LoggedError(error: sanitizedError, app: app, operation: loggedErrorOperation, context: context)
|
|
||||||
try context.save()
|
|
||||||
}
|
|
||||||
catch let saveError
|
|
||||||
{
|
|
||||||
print("[ALTLog] Failed to log error \(sanitizedError.domain) code \(sanitizedError.code) for \(app.bundleIdentifier):", saveError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(_ operations: [Foundation.Operation], context: OperationContext?, requiresSerialQueue: Bool = false)
|
func run(_ operations: [Foundation.Operation], context: OperationContext?, requiresSerialQueue: Bool = false)
|
||||||
{
|
{
|
||||||
// Find "Install AltStore" operation if it already exists in `context`
|
// Find "Install AltStore" operation if it already exists in `context`
|
||||||
|
|||||||
@@ -22,13 +22,27 @@ extension AppManager
|
|||||||
|
|
||||||
var managedObjectContext: NSManagedObjectContext?
|
var managedObjectContext: NSManagedObjectContext?
|
||||||
|
|
||||||
var errorDescription: String? {
|
var localizedTitle: String? {
|
||||||
if let error = self.primaryError
|
var localizedTitle: String?
|
||||||
{
|
self.managedObjectContext?.performAndWait {
|
||||||
return error.localizedDescription
|
if self.sources?.count == 1 {
|
||||||
|
localizedTitle = NSLocalizedString("Failed to refresh Store", comment: "")
|
||||||
|
} else if self.errors.count == 1 {
|
||||||
|
guard let source = self.errors.keys.first else { return }
|
||||||
|
localizedTitle = String(format: NSLocalizedString("Failed to refresh Source '%@'", comment: ""), source.name)
|
||||||
|
} else {
|
||||||
|
localizedTitle = String(format: NSLocalizedString("Failed to refresh %@ Sources", comment: ""), NSNumber(value: self.errors.count))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
return localizedTitle
|
||||||
{
|
}
|
||||||
|
|
||||||
|
var errorDescription: String? {
|
||||||
|
if let error = self.primaryError {
|
||||||
|
return error.localizedDescription
|
||||||
|
} else if let error = self.errors.values.first, self.errors.count == 1 {
|
||||||
|
return error.localizedDescription
|
||||||
|
} else {
|
||||||
var localizedDescription: String?
|
var localizedDescription: String?
|
||||||
|
|
||||||
self.managedObjectContext?.performAndWait {
|
self.managedObjectContext?.performAndWait {
|
||||||
@@ -67,8 +81,14 @@ extension AppManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
var errorUserInfo: [String : Any] {
|
var errorUserInfo: [String : Any] {
|
||||||
guard let error = self.errors.values.first, self.errors.count == 1 else { return [:] }
|
let errors = Array(self.errors.values)
|
||||||
return [NSUnderlyingErrorKey: error]
|
var userInfo = [String: Any]()
|
||||||
|
userInfo[ALTLocalizedTitleErrorKey] = self.localizedTitle
|
||||||
|
userInfo[NSUnderlyingErrorKey] = self.primaryError
|
||||||
|
if #available(iOS 14.5, *), !errors.isEmpty {
|
||||||
|
userInfo[NSMultipleUnderlyingErrorsKey] = errors
|
||||||
|
}
|
||||||
|
return userInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
init(_ error: Error)
|
init(_ error: Error)
|
||||||
|
|||||||
@@ -155,6 +155,13 @@ final class MyAppsViewController: UICollectionViewController
|
|||||||
@IBAction func unwindToMyAppsViewController(_ segue: UIStoryboardSegue)
|
@IBAction func unwindToMyAppsViewController(_ segue: UIStoryboardSegue)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
var minimuxerStatus: Bool {
|
||||||
|
guard minimuxer.ready() else {
|
||||||
|
ToastView(error: (OperationError.noWiFi as NSError).withLocalizedTitle("No WiFi or VPN!")).show(in: self)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension MyAppsViewController
|
private extension MyAppsViewController
|
||||||
@@ -188,7 +195,7 @@ private extension MyAppsViewController
|
|||||||
func makeUpdatesDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>
|
func makeUpdatesDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>
|
||||||
{
|
{
|
||||||
let fetchRequest = InstalledApp.updatesFetchRequest()
|
let fetchRequest = InstalledApp.updatesFetchRequest()
|
||||||
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \InstalledApp.storeApp?.latestVersion?.date, ascending: true),
|
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \InstalledApp.storeApp?.latestSupportedVersion?.date, ascending: false),
|
||||||
NSSortDescriptor(keyPath: \InstalledApp.name, ascending: true)]
|
NSSortDescriptor(keyPath: \InstalledApp.name, ascending: true)]
|
||||||
fetchRequest.returnsObjectsAsFaults = false
|
fetchRequest.returnsObjectsAsFaults = false
|
||||||
|
|
||||||
@@ -197,21 +204,21 @@ private extension MyAppsViewController
|
|||||||
dataSource.cellIdentifierHandler = { _ in "UpdateCell" }
|
dataSource.cellIdentifierHandler = { _ in "UpdateCell" }
|
||||||
dataSource.cellConfigurationHandler = { [weak self] (cell, installedApp, indexPath) in
|
dataSource.cellConfigurationHandler = { [weak self] (cell, installedApp, indexPath) in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard let app = installedApp.storeApp, let latestVersion = app.latestVersion else { return }
|
guard let app = installedApp.storeApp, let latestSupportedVersion = app.latestSupportedVersion else { return }
|
||||||
|
|
||||||
let cell = cell as! UpdateCollectionViewCell
|
let cell = cell as! UpdateCollectionViewCell
|
||||||
cell.layoutMargins.left = self.view.layoutMargins.left
|
cell.layoutMargins.left = self.view.layoutMargins.left
|
||||||
cell.layoutMargins.right = self.view.layoutMargins.right
|
cell.layoutMargins.right = self.view.layoutMargins.right
|
||||||
|
|
||||||
cell.tintColor = app.tintColor ?? .altPrimary
|
cell.tintColor = app.tintColor ?? .altPrimary
|
||||||
cell.versionDescriptionTextView.text = app.versionDescription
|
cell.versionDescriptionTextView.text = latestSupportedVersion.localizedDescription
|
||||||
|
|
||||||
cell.bannerView.iconImageView.image = nil
|
cell.bannerView.iconImageView.image = nil
|
||||||
cell.bannerView.iconImageView.isIndicatingActivity = true
|
cell.bannerView.iconImageView.isIndicatingActivity = true
|
||||||
|
|
||||||
cell.bannerView.configure(for: app)
|
cell.bannerView.configure(for: app)
|
||||||
|
|
||||||
let versionDate = Date().relativeDateString(since: latestVersion.date, dateFormatter: self.dateFormatter)
|
let versionDate = Date().relativeDateString(since: latestSupportedVersion.date, dateFormatter: self.dateFormatter)
|
||||||
cell.bannerView.subtitleLabel.text = versionDate
|
cell.bannerView.subtitleLabel.text = versionDate
|
||||||
|
|
||||||
let appName: String
|
let appName: String
|
||||||
@@ -225,7 +232,7 @@ private extension MyAppsViewController
|
|||||||
appName = app.name
|
appName = app.name
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.bannerView.accessibilityLabel = String(format: NSLocalizedString("%@ %@ update. Released on %@.", comment: ""), appName, latestVersion.version, versionDate)
|
cell.bannerView.accessibilityLabel = String(format: NSLocalizedString("%@ %@ update. Released on %@.", comment: ""), appName, latestSupportedVersion.version, versionDate)
|
||||||
|
|
||||||
cell.bannerView.button.isIndicatingActivity = false
|
cell.bannerView.button.isIndicatingActivity = false
|
||||||
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.updateApp(_:)), for: .primaryActionTriggered)
|
cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.updateApp(_:)), for: .primaryActionTriggered)
|
||||||
@@ -329,21 +336,25 @@ private extension MyAppsViewController
|
|||||||
let currentDate = Date()
|
let currentDate = Date()
|
||||||
|
|
||||||
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
||||||
let numberOfDaysText: String
|
|
||||||
|
|
||||||
if numberOfDays == 1
|
let formatter = DateComponentsFormatter()
|
||||||
{
|
formatter.unitsStyle = .full
|
||||||
numberOfDaysText = NSLocalizedString("1 day", comment: "")
|
formatter.includesApproximationPhrase = false
|
||||||
}
|
formatter.includesTimeRemainingPhrase = false
|
||||||
else
|
|
||||||
{
|
formatter.allowedUnits = [.day, .hour, .minute]
|
||||||
numberOfDaysText = String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
|
||||||
}
|
formatter.maximumUnitCount = 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cell.bannerView.button.setTitle(formatter.string(from: currentDate, to: installedApp.expirationDate)?.uppercased(), for: .normal)
|
||||||
|
|
||||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
|
||||||
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
|
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
|
||||||
|
|
||||||
cell.bannerView.accessibilityLabel? += ". " + String(format: NSLocalizedString("Expires in %@", comment: ""), numberOfDaysText)
|
formatter.includesTimeRemainingPhrase = true
|
||||||
|
|
||||||
|
cell.bannerView.accessibilityLabel? += ". " + (formatter.string(from: currentDate, to: installedApp.expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " "
|
||||||
|
|
||||||
// Make sure refresh button is correct size.
|
// Make sure refresh button is correct size.
|
||||||
cell.layoutIfNeeded()
|
cell.layoutIfNeeded()
|
||||||
@@ -524,11 +535,9 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
guard !failures.isEmpty else { return }
|
guard !failures.isEmpty else { return }
|
||||||
|
|
||||||
let toastView: ToastView
|
|
||||||
|
|
||||||
if let failure = failures.first, results.count == 1
|
if let failure = failures.first, results.count == 1
|
||||||
{
|
{
|
||||||
toastView = ToastView(error: failure.value)
|
ToastView(error: failure.value).show(in: self)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -546,11 +555,10 @@ private extension MyAppsViewController
|
|||||||
let error = failures.first?.value as NSError?
|
let error = failures.first?.value as NSError?
|
||||||
let detailText = error?.localizedFailure ?? error?.localizedFailureReason ?? error?.localizedDescription
|
let detailText = error?.localizedFailure ?? error?.localizedFailureReason ?? error?.localizedDescription
|
||||||
|
|
||||||
toastView = ToastView(text: localizedText, detailText: detailText)
|
let toastView = ToastView(text: localizedText, detailText: detailText, opensLog: true)
|
||||||
toastView.preferredDuration = 4.0
|
toastView.preferredDuration = 4.0
|
||||||
|
toastView.show(in: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
toastView.show(in: self)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.refreshGroup = nil
|
self.refreshGroup = nil
|
||||||
@@ -641,11 +649,7 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
@IBAction func refreshAllApps(_ sender: UIBarButtonItem)
|
@IBAction func refreshAllApps(_ sender: UIBarButtonItem)
|
||||||
{
|
{
|
||||||
if !minimuxer.ready() {
|
guard minimuxerStatus else { return }
|
||||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
|
||||||
toastView.show(in: self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.isRefreshingAllApps = true
|
self.isRefreshingAllApps = true
|
||||||
self.collectionView.collectionViewLayout.invalidateLayout()
|
self.collectionView.collectionViewLayout.invalidateLayout()
|
||||||
@@ -690,8 +694,7 @@ private extension MyAppsViewController
|
|||||||
self.collectionView.reloadItems(at: [indexPath])
|
self.collectionView.reloadItems(at: [indexPath])
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
let toastView = ToastView(error: error)
|
ToastView(error: error, opensLog: true).show(in: self)
|
||||||
toastView.show(in: self)
|
|
||||||
|
|
||||||
self.collectionView.reloadItems(at: [indexPath])
|
self.collectionView.reloadItems(at: [indexPath])
|
||||||
|
|
||||||
@@ -709,11 +712,7 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
@IBAction func sideloadApp(_ sender: UIBarButtonItem)
|
@IBAction func sideloadApp(_ sender: UIBarButtonItem)
|
||||||
{
|
{
|
||||||
if !minimuxer.ready() {
|
guard minimuxerStatus else { return }
|
||||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
|
||||||
toastView.show(in: self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let supportedTypes = UTType.types(tag: "ipa", tagClass: .filenameExtension, conformingTo: nil)
|
let supportedTypes = UTType.types(tag: "ipa", tagClass: .filenameExtension, conformingTo: nil)
|
||||||
|
|
||||||
@@ -784,7 +783,7 @@ private extension MyAppsViewController
|
|||||||
}
|
}
|
||||||
|
|
||||||
let unzipProgress = Progress.discreteProgress(totalUnitCount: 1)
|
let unzipProgress = Progress.discreteProgress(totalUnitCount: 1)
|
||||||
let unzipAppOperation = BlockOperation {
|
let unzipAppOperation = BlockOperation {
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if let error = context.error
|
if let error = context.error
|
||||||
@@ -816,38 +815,7 @@ private extension MyAppsViewController
|
|||||||
{
|
{
|
||||||
unzipAppOperation.addDependency(downloadOperation)
|
unzipAppOperation.addDependency(downloadOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
let removeAppExtensionsProgress = Progress.discreteProgress(totalUnitCount: 1)
|
|
||||||
let removeAppExtensionsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if let error = context.error
|
|
||||||
{
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let application = context.application else { throw OperationError.invalidParameters }
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self?.removeAppExtensions(from: application) { (result) in
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .success: removeAppExtensionsProgress.completedUnitCount = 1
|
|
||||||
case .failure(let error): context.error = error
|
|
||||||
}
|
|
||||||
operation.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
context.error = error
|
|
||||||
operation.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
removeAppExtensionsOperation.addDependency(unzipAppOperation)
|
|
||||||
progress.addChild(removeAppExtensionsProgress, withPendingUnitCount: 5)
|
|
||||||
|
|
||||||
let installProgress = Progress.discreteProgress(totalUnitCount: 100)
|
let installProgress = Progress.discreteProgress(totalUnitCount: 100)
|
||||||
let installAppOperation = RSTAsyncBlockOperation { (operation) in
|
let installAppOperation = RSTAsyncBlockOperation { (operation) in
|
||||||
do
|
do
|
||||||
@@ -896,22 +864,23 @@ private extension MyAppsViewController
|
|||||||
completion(.failure((OperationError.cancelled)))
|
completion(.failure((OperationError.cancelled)))
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
let toastView = ToastView(error: error)
|
ToastView(error: error, opensLog: true).show(in: self)
|
||||||
toastView.show(in: self)
|
|
||||||
|
|
||||||
completion(.failure(error))
|
completion(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
installAppOperation.addDependency(unzipAppOperation)
|
||||||
|
|
||||||
progress.addChild(installProgress, withPendingUnitCount: 65)
|
progress.addChild(installProgress, withPendingUnitCount: 65)
|
||||||
installAppOperation.addDependency(removeAppExtensionsOperation)
|
|
||||||
|
|
||||||
self.sideloadingProgress = progress
|
self.sideloadingProgress = progress
|
||||||
self.sideloadingProgressView.progress = 0
|
self.sideloadingProgressView.progress = 0
|
||||||
self.sideloadingProgressView.isHidden = false
|
self.sideloadingProgressView.isHidden = false
|
||||||
self.sideloadingProgressView.observedProgress = self.sideloadingProgress
|
self.sideloadingProgressView.observedProgress = self.sideloadingProgress
|
||||||
|
|
||||||
let operations = [downloadOperation, unzipAppOperation, removeAppExtensionsOperation, installAppOperation].compactMap { $0 }
|
let operations = [downloadOperation, unzipAppOperation, installAppOperation].compactMap { $0 }
|
||||||
self.operationQueue.addOperations(operations, waitUntilFinished: false)
|
self.operationQueue.addOperations(operations, waitUntilFinished: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -960,49 +929,6 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
cell.bannerView.iconImageView.isIndicatingActivity = false
|
cell.bannerView.iconImageView.isIndicatingActivity = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeAppExtensions(from application: ALTApplication, completion: @escaping (Result<Void, Error>) -> Void)
|
|
||||||
{
|
|
||||||
guard !application.appExtensions.isEmpty else { return completion(.success(())) }
|
|
||||||
|
|
||||||
let firstSentence: String
|
|
||||||
|
|
||||||
if UserDefaults.standard.activeAppLimitIncludesExtensions
|
|
||||||
{
|
|
||||||
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions.", comment: "")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to creating 10 App IDs per week.", comment: "")
|
|
||||||
}
|
|
||||||
|
|
||||||
let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit?", comment: "")
|
|
||||||
|
|
||||||
let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert)
|
|
||||||
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
|
|
||||||
completion(.failure(OperationError.cancelled))
|
|
||||||
}))
|
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in
|
|
||||||
completion(.success(()))
|
|
||||||
})
|
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in
|
|
||||||
do
|
|
||||||
{
|
|
||||||
for appExtension in application.appExtensions
|
|
||||||
{
|
|
||||||
try FileManager.default.removeItem(at: appExtension.fileURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
completion(.success(()))
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
completion(.failure(error))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
self.present(alertController, animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension MyAppsViewController
|
private extension MyAppsViewController
|
||||||
@@ -1012,18 +938,13 @@ private extension MyAppsViewController
|
|||||||
UIApplication.shared.open(installedApp.openAppURL) { success in
|
UIApplication.shared.open(installedApp.openAppURL) { success in
|
||||||
guard !success else { return }
|
guard !success else { return }
|
||||||
|
|
||||||
let toastView = ToastView(error: OperationError.openAppFailed(name: installedApp.name))
|
ToastView(error: OperationError.openAppFailed(name: installedApp.name), opensLog: true).show(in: self)
|
||||||
toastView.show(in: self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func refresh(_ installedApp: InstalledApp)
|
func refresh(_ installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
if !minimuxer.ready() {
|
guard minimuxerStatus else { return }
|
||||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
|
||||||
toastView.show(in: self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let previousProgress = AppManager.shared.refreshProgress(for: installedApp)
|
let previousProgress = AppManager.shared.refreshProgress(for: installedApp)
|
||||||
guard previousProgress == nil else {
|
guard previousProgress == nil else {
|
||||||
@@ -1046,11 +967,7 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
func activate(_ installedApp: InstalledApp)
|
func activate(_ installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
if !minimuxer.ready() {
|
guard minimuxerStatus else { return }
|
||||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
|
||||||
toastView.show(in: self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func finish(_ result: Result<InstalledApp, Error>)
|
func finish(_ result: Result<InstalledApp, Error>)
|
||||||
{
|
{
|
||||||
@@ -1072,8 +989,7 @@ private extension MyAppsViewController
|
|||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
installedApp.isActive = false
|
installedApp.isActive = false
|
||||||
|
|
||||||
let toastView = ToastView(error: error)
|
ToastView(error: error, opensLog: true).show(in: self)
|
||||||
toastView.show(in: self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1127,12 +1043,8 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
func deactivate(_ installedApp: InstalledApp, completionHandler: ((Result<InstalledApp, Error>) -> Void)? = nil)
|
func deactivate(_ installedApp: InstalledApp, completionHandler: ((Result<InstalledApp, Error>) -> Void)? = nil)
|
||||||
{
|
{
|
||||||
guard installedApp.isActive else { return }
|
guard installedApp.isActive, minimuxerStatus else { return }
|
||||||
if !minimuxer.ready() {
|
|
||||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
|
||||||
toastView.show(in: self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
installedApp.isActive = false
|
installedApp.isActive = false
|
||||||
|
|
||||||
AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in
|
AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in
|
||||||
@@ -1145,13 +1057,12 @@ private extension MyAppsViewController
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
print("Failed to activate app:", error)
|
print("Failed to deactivate app:", error)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
installedApp.isActive = true
|
installedApp.isActive = true
|
||||||
|
|
||||||
let toastView = ToastView(error: error)
|
ToastView(error: error, opensLog: true).show(in: self)
|
||||||
toastView.show(in: self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1173,7 +1084,7 @@ private extension MyAppsViewController
|
|||||||
message = NSLocalizedString("This will also erase all backup data for this app.", comment: "")
|
message = NSLocalizedString("This will also erase all backup data for this app.", comment: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
|
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||||
alertController.addAction(.cancel)
|
alertController.addAction(.cancel)
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove", comment: ""), style: .destructive, handler: { (action) in
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove", comment: ""), style: .destructive, handler: { (action) in
|
||||||
AppManager.shared.remove(installedApp) { (result) in
|
AppManager.shared.remove(installedApp) { (result) in
|
||||||
@@ -1182,8 +1093,7 @@ private extension MyAppsViewController
|
|||||||
case .success: break
|
case .success: break
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let toastView = ToastView(error: error)
|
ToastView(error: error, opensLog: true).show(in: self)
|
||||||
toastView.show(in: self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1194,11 +1104,8 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
func backup(_ installedApp: InstalledApp)
|
func backup(_ installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
if !minimuxer.ready() {
|
guard minimuxerStatus else { return }
|
||||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
|
||||||
toastView.show(in: self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let title = NSLocalizedString("Start Backup?", comment: "")
|
let title = NSLocalizedString("Start Backup?", comment: "")
|
||||||
let message = NSLocalizedString("This will replace any previous backups. Please leave SideStore open until the backup is complete.", comment: "")
|
let message = NSLocalizedString("This will replace any previous backups. Please leave SideStore open until the backup is complete.", comment: "")
|
||||||
|
|
||||||
@@ -1220,9 +1127,8 @@ private extension MyAppsViewController
|
|||||||
print("Failed to back up app:", error)
|
print("Failed to back up app:", error)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let toastView = ToastView(error: error)
|
ToastView(error: error, opensLog: true).show(in: self)
|
||||||
toastView.show(in: self)
|
|
||||||
|
|
||||||
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
|
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1238,11 +1144,8 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
func restore(_ installedApp: InstalledApp)
|
func restore(_ installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
if !minimuxer.ready() {
|
guard minimuxerStatus else { return }
|
||||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
|
||||||
toastView.show(in: self)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name)
|
let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name)
|
||||||
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet)
|
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet)
|
||||||
alertController.addAction(.cancel)
|
alertController.addAction(.cancel)
|
||||||
@@ -1260,8 +1163,7 @@ private extension MyAppsViewController
|
|||||||
print("Failed to restore app:", error)
|
print("Failed to restore app:", error)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let toastView = ToastView(error: error)
|
ToastView(error: error, opensLog: true).show(in: self)
|
||||||
toastView.show(in: self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1336,8 +1238,7 @@ private extension MyAppsViewController
|
|||||||
print("Failed to change app icon.", error)
|
print("Failed to change app icon.", error)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
let toastView = ToastView(error: error)
|
ToastView(error: error, opensLog: true).show(in: self)
|
||||||
toastView.show(in: self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1346,24 +1247,28 @@ private extension MyAppsViewController
|
|||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
func enableJIT(for installedApp: InstalledApp)
|
func enableJIT(for installedApp: InstalledApp)
|
||||||
{
|
{
|
||||||
if #available(iOS 17, *) {
|
|
||||||
let toastView = ToastView(error: OperationError.tooNewError)
|
let sidejitenabled = UserDefaults.standard.sidejitenable
|
||||||
toastView.show(in: self)
|
|
||||||
return
|
if #unavailable(iOS 17) {
|
||||||
}
|
guard minimuxerStatus else { return }
|
||||||
if !minimuxer.ready() {
|
}
|
||||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
|
||||||
toastView.show(in: self)
|
|
||||||
|
if #available(iOS 17, *), !sidejitenabled {
|
||||||
|
ToastView(error: (OperationError.tooNewError as NSError).withLocalizedTitle("No iOS 17 On Device JIT!"), opensLog: true).show(in: self)
|
||||||
|
AppManager.shared.log(OperationError.tooNewError, operation: .enableJIT, app: installedApp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
AppManager.shared.enableJIT(for: installedApp) { result in
|
AppManager.shared.enableJIT(for: installedApp) { result in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .success: break
|
case .success: break
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
let toastView = ToastView(error: error)
|
ToastView(error: error, opensLog: true).show(in: self)
|
||||||
toastView.show(in: self.navigationController?.view ?? self.view, duration: 5)
|
AppManager.shared.log(error, operation: .enableJIT, app: installedApp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,9 +313,8 @@ private extension NewsViewController
|
|||||||
{
|
{
|
||||||
case .failure(OperationError.cancelled): break // Ignore
|
case .failure(OperationError.cancelled): break // Ignore
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
let toastView = ToastView(error: error)
|
ToastView(error: error, opensLog: true).show(in: self)
|
||||||
toastView.show(in: self)
|
|
||||||
|
|
||||||
case .success: print("Installed app:", storeApp.bundleIdentifier)
|
case .success: print("Installed app:", storeApp.bundleIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,9 +390,9 @@ extension NewsViewController
|
|||||||
let progress = AppManager.shared.installationProgress(for: storeApp)
|
let progress = AppManager.shared.installationProgress(for: storeApp)
|
||||||
footerView.bannerView.button.progress = progress
|
footerView.bannerView.button.progress = progress
|
||||||
|
|
||||||
if let versionDate = storeApp.latestVersion?.date, versionDate > Date()
|
if let versionDate = storeApp.latestSupportedVersion?.date, versionDate > Date()
|
||||||
{
|
{
|
||||||
footerView.bannerView.button.countdownDate = storeApp.versionDate
|
footerView.bannerView.button.countdownDate = versionDate
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import Network
|
|||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import minimuxer
|
||||||
|
|
||||||
enum AuthenticationError: LocalizedError
|
typealias AuthenticationError = AuthenticationErrorCode.Error
|
||||||
|
enum AuthenticationErrorCode: Int, ALTErrorEnum, CaseIterable
|
||||||
{
|
{
|
||||||
case noTeam
|
case noTeam
|
||||||
case noCertificate
|
case noCertificate
|
||||||
@@ -22,11 +24,11 @@ enum AuthenticationError: LocalizedError
|
|||||||
case missingPrivateKey
|
case missingPrivateKey
|
||||||
case missingCertificate
|
case missingCertificate
|
||||||
|
|
||||||
var errorDescription: String? {
|
var errorFailureReason: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .noTeam: return NSLocalizedString("Developer team could not be found.", comment: "")
|
case .noTeam: return NSLocalizedString("Your Apple ID has no developer teams?", comment: "")
|
||||||
|
case .noCertificate: return NSLocalizedString("The developer certificate could not be found.", comment: "")
|
||||||
case .teamSelectorError: return NSLocalizedString("Error presenting team selector view.", comment: "")
|
case .teamSelectorError: return NSLocalizedString("Error presenting team selector view.", comment: "")
|
||||||
case .noCertificate: return NSLocalizedString("Developer certificate could not be found.", comment: "")
|
|
||||||
case .missingPrivateKey: return NSLocalizedString("The certificate's private key could not be found.", comment: "")
|
case .missingPrivateKey: return NSLocalizedString("The certificate's private key could not be found.", comment: "")
|
||||||
case .missingCertificate: return NSLocalizedString("The certificate could not be found.", comment: "")
|
case .missingCertificate: return NSLocalizedString("The certificate could not be found.", comment: "")
|
||||||
}
|
}
|
||||||
@@ -212,8 +214,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
|||||||
guard
|
guard
|
||||||
let account = Account.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Account.identifier), altTeam.account.identifier), in: context),
|
let account = Account.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Account.identifier), altTeam.account.identifier), in: context),
|
||||||
let team = Team.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Team.identifier), altTeam.identifier), in: context)
|
let team = Team.first(satisfying: NSPredicate(format: "%K == %@", #keyPath(Team.identifier), altTeam.identifier), in: context)
|
||||||
else { throw AuthenticationError.noTeam }
|
else { throw AuthenticationError(.noTeam) }
|
||||||
|
|
||||||
// Account
|
// Account
|
||||||
account.isActiveAccount = true
|
account.isActiveAccount = true
|
||||||
|
|
||||||
@@ -431,7 +433,7 @@ private extension AuthenticationOperation
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
completionHandler(.failure(error ?? OperationError.unknown))
|
completionHandler(.failure(error ?? OperationError.unknown()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -448,7 +450,7 @@ private extension AuthenticationOperation
|
|||||||
if let team = teams.first {
|
if let team = teams.first {
|
||||||
return completionHandler(.success(team))
|
return completionHandler(.success(team))
|
||||||
} else {
|
} else {
|
||||||
return completionHandler(.failure(AuthenticationError.noTeam))
|
return completionHandler(.failure(AuthenticationError(.noTeam)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
@@ -459,7 +461,7 @@ private extension AuthenticationOperation
|
|||||||
|
|
||||||
if !self.present(selectTeamViewController)
|
if !self.present(selectTeamViewController)
|
||||||
{
|
{
|
||||||
return completionHandler(.failure(AuthenticationError.noTeam))
|
return completionHandler(.failure(AuthenticationError(.noTeam)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -488,20 +490,20 @@ private extension AuthenticationOperation
|
|||||||
{
|
{
|
||||||
func requestCertificate()
|
func requestCertificate()
|
||||||
{
|
{
|
||||||
let machineName = "AltStore - " + UIDevice.current.name
|
let machineName: String = "SideStore - \(team.account.firstName)'s \(UIDevice.current.name)"
|
||||||
ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team, session: session) { (certificate, error) in
|
ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team, session: session) { (certificate, error) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let certificate = try Result(certificate, error).get()
|
let certificate = try Result(certificate, error).get()
|
||||||
guard let privateKey = certificate.privateKey else { throw AuthenticationError.missingPrivateKey }
|
guard let privateKey = certificate.privateKey else { throw AuthenticationError(.missingPrivateKey) }
|
||||||
|
|
||||||
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
|
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let certificates = try Result(certificates, error).get()
|
let certificates = try Result(certificates, error).get()
|
||||||
|
|
||||||
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) else {
|
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) else {
|
||||||
throw AuthenticationError.missingCertificate
|
throw AuthenticationError(.missingCertificate)
|
||||||
}
|
}
|
||||||
|
|
||||||
certificate.privateKey = privateKey
|
certificate.privateKey = privateKey
|
||||||
@@ -522,16 +524,50 @@ private extension AuthenticationOperation
|
|||||||
|
|
||||||
func replaceCertificate(from certificates: [ALTCertificate])
|
func replaceCertificate(from certificates: [ALTCertificate])
|
||||||
{
|
{
|
||||||
guard let certificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true }) ?? certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) }
|
let ourCertificates = certificates.filter { a in
|
||||||
|
a.machineName?.starts(with: "SideStore") == true || a.machineName?.starts(with: "AltStore") == true
|
||||||
|
}
|
||||||
|
|
||||||
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in
|
if ourCertificates.isEmpty {
|
||||||
if let error = error, !success
|
return requestCertificate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't have private keys for any of the certificates,
|
||||||
|
// so we need to revoke one and create a new one.
|
||||||
|
var certsText = ""
|
||||||
|
for certificate in ourCertificates {
|
||||||
|
if let name = certificate.machineName {
|
||||||
|
certsText.append("\(name)\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let alertController = UIAlertController(title: NSLocalizedString("Would you like to revoke your previous certificates?\n\(certsText)", comment: ""), message: nil, preferredStyle: .alert)
|
||||||
|
|
||||||
|
let noAction = UIAlertAction(title: NSLocalizedString("No", comment: ""), style: .default) { (action) in
|
||||||
|
requestCertificate()
|
||||||
|
}
|
||||||
|
let yesAction = UIAlertAction(title: NSLocalizedString("Yes", comment: ""), style: .default) { (action) in
|
||||||
|
for certificate in ourCertificates {
|
||||||
|
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in
|
||||||
|
if let error = error, !success
|
||||||
|
{
|
||||||
|
completionHandler(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
requestCertificate()
|
||||||
|
}
|
||||||
|
alertController.addAction(noAction)
|
||||||
|
alertController.addAction(yesAction)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if self.navigationController.presentingViewController != nil
|
||||||
{
|
{
|
||||||
completionHandler(.failure(error))
|
self.navigationController.present(alertController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
requestCertificate()
|
self.presentingViewController?.present(alertController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -579,8 +615,6 @@ private extension AuthenticationOperation
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// We don't have private keys for any of the certificates,
|
|
||||||
// so we need to revoke one and create a new one.
|
|
||||||
replaceCertificate(from: certificates)
|
replaceCertificate(from: certificates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -593,7 +627,7 @@ private extension AuthenticationOperation
|
|||||||
|
|
||||||
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
||||||
{
|
{
|
||||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else {
|
guard let udid = fetch_udid()?.toString() else {
|
||||||
return completionHandler(.failure(OperationError.unknownUDID))
|
return completionHandler(.failure(OperationError.unknownUDID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,12 @@ import AltStoreCore
|
|||||||
import EmotionalDamage
|
import EmotionalDamage
|
||||||
import minimuxer
|
import minimuxer
|
||||||
|
|
||||||
enum RefreshError: LocalizedError
|
typealias RefreshError = RefreshErrorCode.Error
|
||||||
|
enum RefreshErrorCode: Int, ALTErrorEnum, CaseIterable
|
||||||
{
|
{
|
||||||
case noInstalledApps
|
case noInstalledApps
|
||||||
|
|
||||||
var errorDescription: String? {
|
var errorFailureReason: String {
|
||||||
switch self
|
switch self
|
||||||
{
|
{
|
||||||
case .noInstalledApps: return NSLocalizedString("No active apps require refreshing.", comment: "")
|
case .noInstalledApps: return NSLocalizedString("No active apps require refreshing.", comment: "")
|
||||||
@@ -94,7 +95,7 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
|
|||||||
super.main()
|
super.main()
|
||||||
|
|
||||||
guard !self.installedApps.isEmpty else {
|
guard !self.installedApps.isEmpty else {
|
||||||
self.finish(.failure(RefreshError.noInstalledApps))
|
self.finish(.failure(RefreshError(.noInstalledApps)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||||
@@ -107,8 +108,7 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
|
|||||||
}
|
}
|
||||||
if #available(iOS 17, *) {
|
if #available(iOS 17, *) {
|
||||||
// TODO: iOS 17 and above have a new JIT implementation that is completely broken in SideStore :(
|
// TODO: iOS 17 and above have a new JIT implementation that is completely broken in SideStore :(
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
start_auto_mounter(documentsDirectory)
|
start_auto_mounter(documentsDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ private extension BackgroundRefreshAppsOperation
|
|||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
|
|
||||||
var shouldPresentAlert = false
|
var shouldPresentAlert = true
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@@ -219,20 +219,18 @@ private extension BackgroundRefreshAppsOperation
|
|||||||
content.title = NSLocalizedString("Refreshed Apps", comment: "")
|
content.title = NSLocalizedString("Refreshed Apps", comment: "")
|
||||||
content.body = NSLocalizedString("All apps have been refreshed.", comment: "")
|
content.body = NSLocalizedString("All apps have been refreshed.", comment: "")
|
||||||
}
|
}
|
||||||
catch RefreshError.noInstalledApps
|
catch ~OperationError.Code.noWiFi, ~RefreshErrorCode.noInstalledApps
|
||||||
{
|
{
|
||||||
shouldPresentAlert = false
|
shouldPresentAlert = false
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
print("Failed to refresh apps in background.", error)
|
print("Failed to refresh apps in background.", error)
|
||||||
|
|
||||||
content.title = NSLocalizedString("Failed to Refresh Apps", comment: "")
|
content.title = NSLocalizedString("Failed to Refresh Apps", comment: "")
|
||||||
content.body = error.localizedDescription
|
content.body = error.localizedDescription
|
||||||
|
|
||||||
shouldPresentAlert = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if shouldPresentAlert
|
if shouldPresentAlert
|
||||||
{
|
{
|
||||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: delay + 1, repeats: false)
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: delay + 1, repeats: false)
|
||||||
|
|||||||
@@ -55,12 +55,15 @@ class BackupAppOperation: ResultOperation<Void>
|
|||||||
let appName = installedApp.name
|
let appName = installedApp.name
|
||||||
self.appName = appName
|
self.appName = appName
|
||||||
|
|
||||||
let altstoreOpenURL = URL(string: "sidestore://")!
|
guard let altstoreApp = InstalledApp.fetchAltStore(in: context) else {
|
||||||
|
throw OperationError.appNotFound(name: appName)
|
||||||
|
}
|
||||||
|
let altstoreOpenURL = altstoreApp.openAppURL
|
||||||
|
|
||||||
var returnURLComponents = URLComponents(url: altstoreOpenURL, resolvingAgainstBaseURL: false)
|
var returnURLComponents = URLComponents(url: altstoreOpenURL, resolvingAgainstBaseURL: false)
|
||||||
returnURLComponents?.host = "appBackupResponse"
|
returnURLComponents?.host = "appBackupResponse"
|
||||||
guard let returnURL = returnURLComponents?.url else { throw OperationError.openAppFailed(name: appName) }
|
guard let returnURL = returnURLComponents?.url else { throw OperationError.openAppFailed(name: appName) }
|
||||||
|
|
||||||
var openURLComponents = URLComponents()
|
var openURLComponents = URLComponents()
|
||||||
openURLComponents.scheme = installedApp.openAppURL.scheme
|
openURLComponents.scheme = installedApp.openAppURL.scheme
|
||||||
openURLComponents.host = self.action.rawValue
|
openURLComponents.host = self.action.rawValue
|
||||||
|
|||||||
208
AltStore/Operations/ClearAppCacheOperation.swift
Normal file
208
AltStore/Operations/ClearAppCacheOperation.swift
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
//
|
||||||
|
// ClearAppCacheOperation.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 9/27/22.
|
||||||
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AltStoreCore
|
||||||
|
/*
|
||||||
|
struct BatchError: ALTLocalizedError
|
||||||
|
{
|
||||||
|
|
||||||
|
enum Code: Int, ALTErrorCode
|
||||||
|
{
|
||||||
|
typealias Error = BatchError
|
||||||
|
|
||||||
|
case batchError
|
||||||
|
}
|
||||||
|
|
||||||
|
var code: Code = .batchError
|
||||||
|
var underlyingErrors: [Error]
|
||||||
|
|
||||||
|
var errorTitle: String?
|
||||||
|
var errorFailure: String?
|
||||||
|
|
||||||
|
init(errors: [Error])
|
||||||
|
{
|
||||||
|
self.underlyingErrors = errors
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorFailureReason: String {
|
||||||
|
guard !self.underlyingErrors.isEmpty else { return NSLocalizedString("An unknown error occured.", comment: "") }
|
||||||
|
|
||||||
|
let errorMessages = self.underlyingErrors.map { $0.localizedDescription }
|
||||||
|
|
||||||
|
let message = errorMessages.joined(separator: "\n\n")
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
@objc(ClearAppCacheOperation)
|
||||||
|
class ClearAppCacheOperation: ResultOperation<Void>
|
||||||
|
{
|
||||||
|
private let coordinator = NSFileCoordinator()
|
||||||
|
private let coordinatorQueue = OperationQueue()
|
||||||
|
|
||||||
|
override init()
|
||||||
|
{
|
||||||
|
self.coordinatorQueue.name = "AltStore - ClearAppCacheOperation Queue"
|
||||||
|
}
|
||||||
|
|
||||||
|
override func main()
|
||||||
|
{
|
||||||
|
super.main()
|
||||||
|
|
||||||
|
var allErrors = [Error]()
|
||||||
|
|
||||||
|
self.clearTemporaryDirectory { result in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
//case .failure(let batchError as BatchError): allErrors.append(contentsOf: batchError.underlyingErrors)
|
||||||
|
case .failure(let error): allErrors.append(error)
|
||||||
|
case .success: break
|
||||||
|
}
|
||||||
|
|
||||||
|
self.removeUninstalledAppBackupDirectories { result in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
//case .failure(let batchError as BatchError): allErrors.append(contentsOf: batchError.underlyingErrors)
|
||||||
|
case .failure(let error): allErrors.append(error)
|
||||||
|
case .success: break
|
||||||
|
}
|
||||||
|
|
||||||
|
if allErrors.isEmpty
|
||||||
|
{
|
||||||
|
self.finish(.success(()))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.finish(.failure(OperationError.cacheClearError(errors: allErrors.map({ error in
|
||||||
|
return error.localizedDescription
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ClearAppCacheOperation
|
||||||
|
{
|
||||||
|
func clearTemporaryDirectory(completion: @escaping (Result<Void, Error>) -> Void)
|
||||||
|
{
|
||||||
|
let intent = NSFileAccessIntent.writingIntent(with: FileManager.default.temporaryDirectory, options: [.forDeleting])
|
||||||
|
self.coordinator.coordinate(with: [intent], queue: self.coordinatorQueue) { (error) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if let error
|
||||||
|
{
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileURLs = try FileManager.default.contentsOfDirectory(at: intent.url,
|
||||||
|
includingPropertiesForKeys: [],
|
||||||
|
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
|
||||||
|
var errors = [Error]()
|
||||||
|
|
||||||
|
for fileURL in fileURLs
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
print("[ALTLog] Removing item from temporary directory:", fileURL.lastPathComponent)
|
||||||
|
try FileManager.default.removeItem(at: fileURL)
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("[ALTLog] Failed to remove \(fileURL.lastPathComponent) from temporary directory.", error)
|
||||||
|
errors.append(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.isEmpty
|
||||||
|
{
|
||||||
|
completion(.failure(OperationError.cacheClearError(errors: errors.map({ error in
|
||||||
|
return error.localizedDescription
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeUninstalledAppBackupDirectories(completion: @escaping (Result<Void, Error>) -> Void)
|
||||||
|
{
|
||||||
|
guard let backupsDirectory = FileManager.default.appBackupsDirectory else { return completion(.failure(OperationError.missingAppGroup)) }
|
||||||
|
|
||||||
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||||
|
let installedAppBundleIDs = Set(InstalledApp.all(in: context).map { $0.bundleIdentifier })
|
||||||
|
|
||||||
|
let intent = NSFileAccessIntent.writingIntent(with: backupsDirectory, options: [.forDeleting])
|
||||||
|
self.coordinator.coordinate(with: [intent], queue: self.coordinatorQueue) { (error) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if let error
|
||||||
|
{
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
var isDirectory: ObjCBool = false
|
||||||
|
guard FileManager.default.fileExists(atPath: intent.url.path, isDirectory: &isDirectory), isDirectory.boolValue else {
|
||||||
|
completion(.success(()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileURLs = try FileManager.default.contentsOfDirectory(at: intent.url,
|
||||||
|
includingPropertiesForKeys: [.isDirectoryKey, .nameKey],
|
||||||
|
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
|
||||||
|
var errors = [Error]()
|
||||||
|
|
||||||
|
|
||||||
|
for backupDirectory in fileURLs
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let resourceValues = try backupDirectory.resourceValues(forKeys: [.isDirectoryKey, .nameKey])
|
||||||
|
guard let isDirectory = resourceValues.isDirectory, let bundleID = resourceValues.name else { continue }
|
||||||
|
|
||||||
|
if isDirectory && !installedAppBundleIDs.contains(bundleID) && !AppManager.shared.isActivelyManagingApp(withBundleID: bundleID)
|
||||||
|
{
|
||||||
|
print("[ALTLog] Removing backup directory for uninstalled app:", bundleID)
|
||||||
|
try FileManager.default.removeItem(at: backupDirectory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("[ALTLog] Failed to remove app backup directory:", error)
|
||||||
|
errors.append(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.isEmpty
|
||||||
|
{
|
||||||
|
completion(.failure(OperationError.cacheClearError(errors: errors.map({ error in
|
||||||
|
return error.localizedDescription
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completion(.success(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("[ALTLog] Failed to remove app backup directory:", error)
|
||||||
|
completion(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,66 +12,108 @@ import Roxas
|
|||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
|
||||||
private extension DownloadAppOperation
|
|
||||||
{
|
|
||||||
struct DependencyError: ALTLocalizedError
|
|
||||||
{
|
|
||||||
let dependency: Dependency
|
|
||||||
let error: Error
|
|
||||||
|
|
||||||
var failure: String? {
|
|
||||||
return String(format: NSLocalizedString("Could not download “%@”.", comment: ""), self.dependency.preferredFilename)
|
|
||||||
}
|
|
||||||
|
|
||||||
var underlyingError: Error? {
|
|
||||||
return self.error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc(DownloadAppOperation)
|
@objc(DownloadAppOperation)
|
||||||
final class DownloadAppOperation: ResultOperation<ALTApplication>
|
final class DownloadAppOperation: ResultOperation<ALTApplication>
|
||||||
{
|
{
|
||||||
let app: AppProtocol
|
let app: AppProtocol
|
||||||
let context: AppOperationContext
|
let context: AppOperationContext
|
||||||
|
|
||||||
|
private let appName: String
|
||||||
private let bundleIdentifier: String
|
private let bundleIdentifier: String
|
||||||
private var sourceURL: URL?
|
|
||||||
private let destinationURL: URL
|
private let destinationURL: URL
|
||||||
|
|
||||||
private let session = URLSession(configuration: .default)
|
private let session = URLSession(configuration: .default)
|
||||||
private let temporaryDirectory = FileManager.default.uniqueTemporaryURL()
|
private let temporaryDirectory = FileManager.default.uniqueTemporaryURL()
|
||||||
|
|
||||||
init(app: AppProtocol, destinationURL: URL, context: AppOperationContext)
|
init(app: AppProtocol, destinationURL: URL, context: AppOperationContext)
|
||||||
{
|
{
|
||||||
self.app = app
|
self.app = app
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
|
self.appName = app.name
|
||||||
self.bundleIdentifier = app.bundleIdentifier
|
self.bundleIdentifier = app.bundleIdentifier
|
||||||
self.sourceURL = app.url
|
|
||||||
self.destinationURL = destinationURL
|
self.destinationURL = destinationURL
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
// App = 3, Dependencies = 1
|
// App = 3, Dependencies = 1
|
||||||
self.progress.totalUnitCount = 4
|
self.progress.totalUnitCount = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
override func main()
|
override func main()
|
||||||
{
|
{
|
||||||
super.main()
|
super.main()
|
||||||
|
|
||||||
if let error = self.context.error
|
if let error = self.context.error
|
||||||
{
|
{
|
||||||
self.finish(.failure(error))
|
self.finish(.failure(error))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Downloading App:", self.bundleIdentifier)
|
print("Downloading App:", self.bundleIdentifier)
|
||||||
|
|
||||||
guard let sourceURL = self.sourceURL else { return self.finish(.failure(OperationError.appNotFound)) }
|
self.localizedFailure = String(format: NSLocalizedString("%@ could not be downloaded.", comment: ""), self.appName)
|
||||||
|
|
||||||
self.downloadApp(from: sourceURL) { result in
|
guard let storeApp = self.app as? StoreApp else { return self.download(self.app) }
|
||||||
|
storeApp.managedObjectContext?.perform {
|
||||||
|
do {
|
||||||
|
let latestVersion = try self.verify(storeApp)
|
||||||
|
self.download(latestVersion)
|
||||||
|
} catch let error as VerificationError where error.code == .iOSVersionNotSupported {
|
||||||
|
guard let presentingViewController = self.context.presentingViewController,
|
||||||
|
let latestSupportedVersion = storeApp.latestSupportedVersion,
|
||||||
|
case let version = latestSupportedVersion.version,
|
||||||
|
version != storeApp.installedApp?.version else {
|
||||||
|
return self.finish(.failure(error))
|
||||||
|
}
|
||||||
|
let title = NSLocalizedString("Unsupported iOS Version", comment: "")
|
||||||
|
let message = error.localizedDescription + "\n\n" + NSLocalizedString("Would you like to download the last version compatible with this device instead?", comment: "")
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||||||
|
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style) { _ in
|
||||||
|
self.finish(.failure(OperationError.cancelled))
|
||||||
|
})
|
||||||
|
alertController.addAction(UIAlertAction(title: String(format: NSLocalizedString("Download %@ %@", comment: ""), self.appName, version), style: .default) { _ in
|
||||||
|
self.download(latestSupportedVersion)
|
||||||
|
})
|
||||||
|
presentingViewController.present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
self.finish(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func finish(_ result: Result<ALTApplication, any Error>) {
|
||||||
|
do {
|
||||||
|
try FileManager.default.removeItem(at: self.temporaryDirectory)
|
||||||
|
} catch {
|
||||||
|
print("Failed to remove DownloadAppOperation temporary directory: \(self.temporaryDirectory).", error)
|
||||||
|
}
|
||||||
|
super.finish(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension DownloadAppOperation {
|
||||||
|
func verify(_ storeApp: StoreApp) throws -> AppVersion {
|
||||||
|
guard let version = storeApp.latestAvailableVersion else {
|
||||||
|
let failureReason = String(format: NSLocalizedString("The latest version of %@ could not be determined.", comment: ""), self.appName)
|
||||||
|
throw OperationError.unknown(failureReason: failureReason)
|
||||||
|
}
|
||||||
|
if let minOSVersion = version.minOSVersion, !ProcessInfo.processInfo.isOperatingSystemAtLeast(minOSVersion) {
|
||||||
|
throw VerificationError.iOSVersionNotSupported(app: storeApp, requiredOSVersion: minOSVersion)
|
||||||
|
} else if let maxOSVersion = version.maxOSVersion, ProcessInfo.processInfo.operatingSystemVersion > maxOSVersion {
|
||||||
|
throw VerificationError.iOSVersionNotSupported(app: storeApp, requiredOSVersion: maxOSVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
func download(@Managed _ app: AppProtocol) {
|
||||||
|
guard let sourceURL = $app.url else { return self.finish(.failure(OperationError.appNotFound(name: self.appName))) }
|
||||||
|
|
||||||
|
self.downloadIPA(from: sourceURL) { result in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let application = try result.get()
|
let application = try result.get()
|
||||||
@@ -112,24 +154,7 @@ final class DownloadAppOperation: ResultOperation<ALTApplication>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func finish(_ result: Result<ALTApplication, Error>)
|
func downloadIPA(from sourceURL: URL, completionHandler: @escaping (Result<ALTApplication, Error>) -> Void)
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
try FileManager.default.removeItem(at: self.temporaryDirectory)
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
print("Failed to remove DownloadAppOperation temporary directory: \(self.temporaryDirectory).", error)
|
|
||||||
}
|
|
||||||
|
|
||||||
super.finish(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension DownloadAppOperation
|
|
||||||
{
|
|
||||||
func downloadApp(from sourceURL: URL, completionHandler: @escaping (Result<ALTApplication, Error>) -> Void)
|
|
||||||
{
|
{
|
||||||
func finishOperation(_ result: Result<URL, Error>)
|
func finishOperation(_ result: Result<URL, Error>)
|
||||||
{
|
{
|
||||||
@@ -138,8 +163,8 @@ private extension DownloadAppOperation
|
|||||||
let fileURL = try result.get()
|
let fileURL = try result.get()
|
||||||
|
|
||||||
var isDirectory: ObjCBool = false
|
var isDirectory: ObjCBool = false
|
||||||
guard FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDirectory) else { throw OperationError.appNotFound }
|
guard FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDirectory) else { throw OperationError.appNotFound(name: self.appName) }
|
||||||
|
|
||||||
try FileManager.default.createDirectory(at: self.temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
|
try FileManager.default.createDirectory(at: self.temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||||
|
|
||||||
let appBundleURL: URL
|
let appBundleURL: URL
|
||||||
@@ -178,6 +203,9 @@ private extension DownloadAppOperation
|
|||||||
let downloadTask = self.session.downloadTask(with: sourceURL) { (fileURL, response, error) in
|
let downloadTask = self.session.downloadTask(with: sourceURL) { (fileURL, response, error) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
if let response = response as? HTTPURLResponse {
|
||||||
|
guard response.statusCode != 404 else { throw CocoaError(.fileNoSuchFile, userInfo: [NSURLErrorKey: sourceURL]) }
|
||||||
|
}
|
||||||
let (fileURL, _) = try Result((fileURL, response), error).get()
|
let (fileURL, _) = try Result((fileURL, response), error).get()
|
||||||
finishOperation(.success(fileURL))
|
finishOperation(.success(fileURL))
|
||||||
|
|
||||||
@@ -252,7 +280,7 @@ private extension DownloadAppOperation
|
|||||||
let altstorePlist = try PropertyListDecoder().decode(AltStorePlist.self, from: data)
|
let altstorePlist = try PropertyListDecoder().decode(AltStorePlist.self, from: data)
|
||||||
|
|
||||||
var dependencyURLs = Set<URL>()
|
var dependencyURLs = Set<URL>()
|
||||||
var dependencyError: DependencyError?
|
var dependencyError: Error?
|
||||||
|
|
||||||
let dispatchGroup = DispatchGroup()
|
let dispatchGroup = DispatchGroup()
|
||||||
let progress = Progress(totalUnitCount: Int64(altstorePlist.dependencies.count), parent: self.progress, pendingUnitCount: 1)
|
let progress = Progress(totalUnitCount: Int64(altstorePlist.dependencies.count), parent: self.progress, pendingUnitCount: 1)
|
||||||
@@ -285,7 +313,7 @@ private extension DownloadAppOperation
|
|||||||
}
|
}
|
||||||
catch let error as DecodingError
|
catch let error as DecodingError
|
||||||
{
|
{
|
||||||
let nsError = (error as NSError).withLocalizedFailure(String(format: NSLocalizedString("Could not download dependencies for %@.", comment: ""), application.name))
|
let nsError = (error as NSError).withLocalizedFailure(String(format: NSLocalizedString("Could not determine dependencies for %@.", comment: ""), application.name))
|
||||||
completionHandler(.failure(nsError))
|
completionHandler(.failure(nsError))
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -294,7 +322,7 @@ private extension DownloadAppOperation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func download(_ dependency: Dependency, for application: ALTApplication, progress: Progress, completionHandler: @escaping (Result<URL, DependencyError>) -> Void)
|
func download(_ dependency: Dependency, for application: ALTApplication, progress: Progress, completionHandler: @escaping (Result<URL, Error>) -> Void)
|
||||||
{
|
{
|
||||||
let downloadTask = self.session.downloadTask(with: dependency.downloadURL) { (fileURL, response, error) in
|
let downloadTask = self.session.downloadTask(with: dependency.downloadURL) { (fileURL, response, error) in
|
||||||
do
|
do
|
||||||
@@ -315,9 +343,10 @@ private extension DownloadAppOperation
|
|||||||
|
|
||||||
completionHandler(.success(destinationURL))
|
completionHandler(.success(destinationURL))
|
||||||
}
|
}
|
||||||
catch
|
catch let error as NSError
|
||||||
{
|
{
|
||||||
completionHandler(.failure(DependencyError(dependency: dependency, error: error)))
|
let localizedFailure = String(format: NSLocalizedString("The dependency '%@' could not be downloaded.", comment: ""), dependency.preferredFilename)
|
||||||
|
completionHandler(.failure(error.withLocalizedFailure(localizedFailure)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
progress.addChild(downloadTask.progress, withPendingUnitCount: 1)
|
progress.addChild(downloadTask.progress, withPendingUnitCount: 1)
|
||||||
|
|||||||
@@ -9,9 +9,17 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import minimuxer
|
import minimuxer
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
|
|
||||||
|
enum SideJITServerErrorType: Error {
|
||||||
|
case invalidURL
|
||||||
|
case errorConnecting
|
||||||
|
case deviceNotFound
|
||||||
|
case other(String)
|
||||||
|
}
|
||||||
|
|
||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
protocol EnableJITContext
|
protocol EnableJITContext
|
||||||
{
|
{
|
||||||
@@ -43,21 +51,105 @@ final class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
|||||||
}
|
}
|
||||||
|
|
||||||
guard let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
|
guard let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||||
|
if #available(iOS 17, *) {
|
||||||
installedApp.managedObjectContext?.perform {
|
let sideJITenabled = UserDefaults.standard.sidejitenable
|
||||||
var retries = 3
|
let SideJITIP = UserDefaults.standard.textInputSideJITServerurl ?? ""
|
||||||
while (retries > 0){
|
|
||||||
do {
|
if sideJITenabled {
|
||||||
try debug_app(installedApp.resignedBundleIdentifier)
|
installedApp.managedObjectContext?.perform {
|
||||||
self.finish(.success(()))
|
EnableJITSideJITServer(serverurl: SideJITIP, installedapp: installedApp) { result in
|
||||||
retries = 0
|
switch result {
|
||||||
} catch {
|
case .failure(let error):
|
||||||
retries -= 1
|
switch error {
|
||||||
if (retries <= 0){
|
case .invalidURL, .errorConnecting:
|
||||||
self.finish(.failure(error))
|
self.finish(.failure(OperationError.unableToConnectSideJIT))
|
||||||
|
case .deviceNotFound:
|
||||||
|
self.finish(.failure(OperationError.unableToRespondSideJITDevice))
|
||||||
|
case .other(let message):
|
||||||
|
if let startRange = message.range(of: "<p>"),
|
||||||
|
let endRange = message.range(of: "</p>", range: startRange.upperBound..<message.endIndex) {
|
||||||
|
let pContent = message[startRange.upperBound..<endRange.lowerBound]
|
||||||
|
self.finish(.failure(OperationError.SideJITIssue(error: String(pContent))))
|
||||||
|
print(message + " + " + String(pContent))
|
||||||
|
} else {
|
||||||
|
print(message)
|
||||||
|
self.finish(.failure(OperationError.SideJITIssue(error: message)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .success():
|
||||||
|
self.finish(.success(()))
|
||||||
|
print("Thank you for using this, it was made by Stossy11 and tested by trolley or sniper1239408")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
installedApp.managedObjectContext?.perform {
|
||||||
|
var retries = 3
|
||||||
|
while (retries > 0){
|
||||||
|
do {
|
||||||
|
try debug_app(installedApp.resignedBundleIdentifier)
|
||||||
|
self.finish(.success(()))
|
||||||
|
retries = 0
|
||||||
|
} catch {
|
||||||
|
retries -= 1
|
||||||
|
if (retries <= 0){
|
||||||
|
self.finish(.failure(error))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOS 17, *)
|
||||||
|
func EnableJITSideJITServer(serverurl: String, installedapp: InstalledApp, completion: @escaping (Result<Void, SideJITServerErrorType>) -> Void) {
|
||||||
|
guard let udid = fetch_udid()?.toString() else {
|
||||||
|
completion(.failure(.other("Unable to get UDID")))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var SJSURL = serverurl
|
||||||
|
|
||||||
|
if (UserDefaults.standard.textInputSideJITServerurl ?? "").isEmpty {
|
||||||
|
SJSURL = "http://sidejitserver._http._tcp.local:8080"
|
||||||
|
}
|
||||||
|
|
||||||
|
if !SJSURL.hasPrefix("http") {
|
||||||
|
completion(.failure(.invalidURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let fullurl = SJSURL + "/\(udid)/" + installedapp.resignedBundleIdentifier
|
||||||
|
|
||||||
|
let url = URL(string: fullurl)!
|
||||||
|
|
||||||
|
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
|
||||||
|
if let error = error {
|
||||||
|
completion(.failure(.errorConnecting))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let data = data, let datastring = String(data: data, encoding: .utf8) else { return }
|
||||||
|
|
||||||
|
if datastring == "Enabled JIT for '\(installedapp.name)'!" {
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
|
content.title = "JIT Successfully Enabled"
|
||||||
|
content.subtitle = "JIT Enabled For \(installedapp.name)"
|
||||||
|
content.sound = UNNotificationSound.default
|
||||||
|
|
||||||
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
|
||||||
|
let request = UNNotificationRequest(identifier: "EnabledJIT", content: content, trigger: nil)
|
||||||
|
|
||||||
|
UNUserNotificationCenter.current().add(request)
|
||||||
|
completion(.success(()))
|
||||||
|
} else {
|
||||||
|
let errorType: SideJITServerErrorType = datastring == "Could not find device!" ? .deviceNotFound : .other(datastring)
|
||||||
|
completion(.failure(errorType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSoc
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.url = AnisetteManager.currentURL
|
self.url = URL(string: UserDefaults.standard.menuAnisetteURL)
|
||||||
print("Anisette URL: \(self.url!.absoluteString)")
|
print("Anisette URL: \(self.url!.absoluteString)")
|
||||||
|
|
||||||
if let identifier = Keychain.shared.identifier,
|
if let identifier = Keychain.shared.identifier,
|
||||||
@@ -408,6 +408,7 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSoc
|
|||||||
func fetchAnisetteV3(_ identifier: String, _ adiPb: String) {
|
func fetchAnisetteV3(_ identifier: String, _ adiPb: String) {
|
||||||
fetchClientInfo {
|
fetchClientInfo {
|
||||||
print("Fetching anisette V3")
|
print("Fetching anisette V3")
|
||||||
|
let url = UserDefaults.standard.menuAnisetteURL
|
||||||
var request = URLRequest(url: self.url!.appendingPathComponent("v3").appendingPathComponent("get_headers"))
|
var request = URLRequest(url: self.url!.appendingPathComponent("v3").appendingPathComponent("get_headers"))
|
||||||
request.httpMethod = "POST"
|
request.httpMethod = "POST"
|
||||||
request.httpBody = try! JSONSerialization.data(withJSONObject: [
|
request.httpBody = try! JSONSerialization.data(withJSONObject: [
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ final class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProv
|
|||||||
let session = self.context.session
|
let session = self.context.session
|
||||||
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||||
|
|
||||||
guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound)) }
|
guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound(name: nil))) }
|
||||||
|
|
||||||
self.progress.totalUnitCount = Int64(1 + app.appExtensions.count)
|
self.progress.totalUnitCount = Int64(1 + app.appExtensions.count)
|
||||||
|
|
||||||
self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in
|
self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in
|
||||||
@@ -260,7 +260,7 @@ extension FetchProvisioningProfilesOperation
|
|||||||
{
|
{
|
||||||
if let expirationDate = sortedExpirationDates.first
|
if let expirationDate = sortedExpirationDates.first
|
||||||
{
|
{
|
||||||
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
throw OperationError.maximumAppIDLimitReached(appName: application.name, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, expirationDate: expirationDate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,7 +286,7 @@ extension FetchProvisioningProfilesOperation
|
|||||||
{
|
{
|
||||||
if let expirationDate = sortedExpirationDates.first
|
if let expirationDate = sortedExpirationDates.first
|
||||||
{
|
{
|
||||||
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
throw OperationError.maximumAppIDLimitReached(appName: application.name, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, expirationDate: expirationDate)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
|||||||
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||||
backgroundContext.perform {
|
backgroundContext.perform {
|
||||||
|
|
||||||
|
|
||||||
/* App */
|
/* App */
|
||||||
let installedApp: InstalledApp
|
let installedApp: InstalledApp
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import Roxas
|
|||||||
class ResultOperation<ResultType>: Operation
|
class ResultOperation<ResultType>: Operation
|
||||||
{
|
{
|
||||||
var resultHandler: ((Result<ResultType, Error>) -> Void)?
|
var resultHandler: ((Result<ResultType, Error>) -> Void)?
|
||||||
|
|
||||||
|
// Should only be set by subclasses
|
||||||
|
var localizedFailure: String?
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
override func finish()
|
override func finish()
|
||||||
{
|
{
|
||||||
@@ -22,16 +25,20 @@ class ResultOperation<ResultType>: Operation
|
|||||||
func finish(_ result: Result<ResultType, Error>)
|
func finish(_ result: Result<ResultType, Error>)
|
||||||
{
|
{
|
||||||
guard !self.isFinished else { return }
|
guard !self.isFinished else { return }
|
||||||
|
|
||||||
|
var result = result
|
||||||
|
|
||||||
if self.isCancelled
|
if self.isCancelled
|
||||||
{
|
{
|
||||||
self.resultHandler?(.failure(OperationError.cancelled))
|
result = .failure(OperationError.cancelled)
|
||||||
}
|
}
|
||||||
else
|
else if case .failure(let nsError as NSError) = result, let localizedFailure, nsError.localizedFailure == nil {
|
||||||
{
|
// Error doesn't have its own localizedFailure, so we give it the Operation's (if it exists)
|
||||||
self.resultHandler?(result)
|
let error = nsError.withLocalizedFailure(localizedFailure)
|
||||||
|
result = .failure(error)
|
||||||
}
|
}
|
||||||
|
self.resultHandler?(result)
|
||||||
|
|
||||||
super.finish()
|
super.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,68 +8,186 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import AltStoreCore
|
||||||
import minimuxer
|
import minimuxer
|
||||||
|
|
||||||
enum OperationError: LocalizedError
|
extension OperationError
|
||||||
{
|
{
|
||||||
static let domain = OperationError.unknown._domain
|
enum Code: Int, ALTErrorCode, CaseIterable {
|
||||||
|
typealias Error = OperationError
|
||||||
|
|
||||||
|
// General
|
||||||
|
case unknown = 1000
|
||||||
|
case unknownResult
|
||||||
|
case cancelled
|
||||||
|
case timedOut
|
||||||
|
case unableToConnectSideJIT
|
||||||
|
case unableToRespondSideJITDevice
|
||||||
|
case wrongSideJITIP
|
||||||
|
case SideJITIssue // (error: String)
|
||||||
|
case refreshsidejit
|
||||||
|
case notAuthenticated
|
||||||
|
case appNotFound
|
||||||
|
case unknownUDID
|
||||||
|
case invalidApp
|
||||||
|
case invalidParameters
|
||||||
|
case maximumAppIDLimitReached//((application: ALTApplication, requiredAppIDs: Int, availableAppIDs: Int, nextExpirationDate: Date)
|
||||||
|
case noSources
|
||||||
|
case openAppFailed//(name: String)
|
||||||
|
case missingAppGroup
|
||||||
|
|
||||||
|
// Connection
|
||||||
|
case noWiFi = 1200
|
||||||
|
case tooNewError
|
||||||
|
case anisetteV1Error//(message: String)
|
||||||
|
case provisioningError//(result: String, message: String?)
|
||||||
|
case anisetteV3Error//(message: String)
|
||||||
|
|
||||||
|
case cacheClearError//(errors: [String])
|
||||||
|
}
|
||||||
|
|
||||||
|
static let unknownResult: OperationError = .init(code: .unknownResult)
|
||||||
|
static let cancelled: OperationError = .init(code: .cancelled)
|
||||||
|
static let timedOut: OperationError = .init(code: .timedOut)
|
||||||
|
static let unableToConnectSideJIT: OperationError = .init(code: .unableToConnectSideJIT)
|
||||||
|
static let unableToRespondSideJITDevice: OperationError = .init(code: .unableToRespondSideJITDevice)
|
||||||
|
static let wrongSideJITIP: OperationError = .init(code: .wrongSideJITIP)
|
||||||
|
static let notAuthenticated: OperationError = .init(code: .notAuthenticated)
|
||||||
|
static let unknownUDID: OperationError = .init(code: .unknownUDID)
|
||||||
|
static let invalidApp: OperationError = .init(code: .invalidApp)
|
||||||
|
static let invalidParameters: OperationError = .init(code: .invalidParameters)
|
||||||
|
static let noSources: OperationError = .init(code: .noSources)
|
||||||
|
static let missingAppGroup: OperationError = .init(code: .missingAppGroup)
|
||||||
|
|
||||||
|
static let noWiFi: OperationError = .init(code: .noWiFi)
|
||||||
|
static let tooNewError: OperationError = .init(code: .tooNewError)
|
||||||
|
static let provisioningError: OperationError = .init(code: .provisioningError)
|
||||||
|
static let anisetteV1Error: OperationError = .init(code: .anisetteV1Error)
|
||||||
|
static let anisetteV3Error: OperationError = .init(code: .anisetteV3Error)
|
||||||
|
|
||||||
|
static let cacheClearError: OperationError = .init(code: .cacheClearError)
|
||||||
|
|
||||||
|
static func unknown(failureReason: String? = nil, file: String = #fileID, line: UInt = #line) -> OperationError {
|
||||||
|
OperationError(code: .unknown, failureReason: failureReason, sourceFile: file, sourceLine: line)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func appNotFound(name: String?) -> OperationError {
|
||||||
|
OperationError(code: .appNotFound, appName: name)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func openAppFailed(name: String?) -> OperationError {
|
||||||
|
OperationError(code: .openAppFailed, appName: name)
|
||||||
|
}
|
||||||
|
|
||||||
case unknown
|
static func SideJITIssue(error: String?) -> OperationError {
|
||||||
case unknownResult
|
var o = OperationError(code: .SideJITIssue)
|
||||||
case cancelled
|
o.errorFailure = error
|
||||||
case timedOut
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
case notAuthenticated
|
static func maximumAppIDLimitReached(appName: String, requiredAppIDs: Int, availableAppIDs: Int, expirationDate: Date) -> OperationError {
|
||||||
case appNotFound
|
OperationError(code: .maximumAppIDLimitReached, appName: appName, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, expirationDate: expirationDate)
|
||||||
|
}
|
||||||
case unknownUDID
|
|
||||||
|
static func provisioningError(result: String, message: String?) -> OperationError {
|
||||||
case invalidApp
|
var o = OperationError(code: .provisioningError, failureReason: result)
|
||||||
case invalidParameters
|
o.errorTitle = message
|
||||||
|
return o
|
||||||
case maximumAppIDLimitReached(application: ALTApplication, requiredAppIDs: Int, availableAppIDs: Int, nextExpirationDate: Date)
|
}
|
||||||
|
|
||||||
case noSources
|
static func cacheClearError(errors: [String]) -> OperationError {
|
||||||
|
OperationError(code: .cacheClearError, failureReason: errors.joined(separator: "\n"))
|
||||||
case openAppFailed(name: String)
|
}
|
||||||
case missingAppGroup
|
|
||||||
|
static func anisetteV1Error(message: String) -> OperationError {
|
||||||
case noWiFi
|
OperationError(code: .anisetteV1Error, failureReason: message)
|
||||||
case tooNewError
|
}
|
||||||
case anisetteV1Error(message: String)
|
|
||||||
case provisioningError(result: String, message: String?)
|
static func anisetteV3Error(message: String) -> OperationError {
|
||||||
case anisetteV3Error(message: String)
|
OperationError(code: .anisetteV3Error, failureReason: message)
|
||||||
|
}
|
||||||
var failureReason: String? {
|
|
||||||
switch self {
|
}
|
||||||
case .unknown: return NSLocalizedString("An unknown error occured.", comment: "")
|
|
||||||
|
|
||||||
|
struct OperationError: ALTLocalizedError {
|
||||||
|
|
||||||
|
let code: Code
|
||||||
|
|
||||||
|
var errorTitle: String?
|
||||||
|
var errorFailure: String?
|
||||||
|
|
||||||
|
var appName: String?
|
||||||
|
|
||||||
|
var requiredAppIDs: Int?
|
||||||
|
var availableAppIDs: Int?
|
||||||
|
var expirationDate: Date?
|
||||||
|
|
||||||
|
var sourceFile: String?
|
||||||
|
var sourceLine: UInt?
|
||||||
|
|
||||||
|
private var _failureReason: String?
|
||||||
|
|
||||||
|
private init(code: Code, failureReason: String? = nil,
|
||||||
|
appName: String? = nil, requiredAppIDs: Int? = nil, availableAppIDs: Int? = nil,
|
||||||
|
expirationDate: Date? = nil, sourceFile: String? = nil, sourceLine: UInt? = nil){
|
||||||
|
self.code = code
|
||||||
|
self._failureReason = failureReason
|
||||||
|
|
||||||
|
self.appName = appName
|
||||||
|
self.requiredAppIDs = requiredAppIDs
|
||||||
|
self.availableAppIDs = availableAppIDs
|
||||||
|
self.expirationDate = expirationDate
|
||||||
|
self.sourceFile = sourceFile
|
||||||
|
self.sourceLine = sourceLine
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorFailureReason: String {
|
||||||
|
switch self.code {
|
||||||
|
case .unknown:
|
||||||
|
var failureReason = self._failureReason ?? NSLocalizedString("An unknown error occurred.", comment: "")
|
||||||
|
guard let sourceFile, let sourceLine else { return failureReason }
|
||||||
|
failureReason += " (\(sourceFile) line \(sourceLine)"
|
||||||
|
return failureReason
|
||||||
case .unknownResult: return NSLocalizedString("The operation returned an unknown result.", comment: "")
|
case .unknownResult: return NSLocalizedString("The operation returned an unknown result.", comment: "")
|
||||||
case .cancelled: return NSLocalizedString("The operation was cancelled.", comment: "")
|
case .cancelled: return NSLocalizedString("The operation was cancelled.", comment: "")
|
||||||
case .timedOut: return NSLocalizedString("The operation timed out.", comment: "")
|
case .timedOut: return NSLocalizedString("The operation timed out.", comment: "")
|
||||||
case .notAuthenticated: return NSLocalizedString("You are not signed in.", comment: "")
|
case .notAuthenticated: return NSLocalizedString("You are not signed in.", comment: "")
|
||||||
case .appNotFound: return NSLocalizedString("App not found.", comment: "")
|
case .unknownUDID: return NSLocalizedString("SideStore could not determine this device's UDID.", comment: "")
|
||||||
case .unknownUDID: return NSLocalizedString("Unknown device UDID.", comment: "")
|
case .invalidApp: return NSLocalizedString("The app is in an invalid format.", comment: "")
|
||||||
case .invalidApp: return NSLocalizedString("The app is invalid.", comment: "")
|
|
||||||
case .invalidParameters: return NSLocalizedString("Invalid parameters.", comment: "")
|
case .invalidParameters: return NSLocalizedString("Invalid parameters.", comment: "")
|
||||||
|
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs within a 7 day period.", comment: "")
|
||||||
case .noSources: return NSLocalizedString("There are no SideStore sources.", comment: "")
|
case .noSources: return NSLocalizedString("There are no SideStore sources.", comment: "")
|
||||||
case .openAppFailed(let name): return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), name)
|
case .missingAppGroup: return NSLocalizedString("SideStore's shared app group could not be accessed.", comment: "")
|
||||||
case .missingAppGroup: return NSLocalizedString("SideStore's shared app group could not be found.", comment: "")
|
case .appNotFound:
|
||||||
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs.", comment: "")
|
let appName = self.appName ?? NSLocalizedString("The app", comment: "")
|
||||||
case .noWiFi: return NSLocalizedString("You do not appear to be connected to WiFi!\nSideStore will never be able to install or refresh applications without WiFi.", comment: "")
|
return String(format: NSLocalizedString("%@ could not be found.", comment: ""), appName)
|
||||||
case .tooNewError: return NSLocalizedString("iOS 17 has changed how JIT is enabled therefore SideStore cannot enable it at this time, sorry for any inconvenience.\nWe will let everyone know once we have a solution!", comment: "")
|
case .openAppFailed:
|
||||||
case .anisetteV1Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: ""), message)
|
let appName = self.appName ?? NSLocalizedString("The app", comment: "")
|
||||||
case .provisioningError(let result, let message): return String(format: NSLocalizedString("An error occurred when provisioning: %@%@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), result, message != nil ? (" (" + message! + ")") : "")
|
return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), appName)
|
||||||
case .anisetteV3Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), message)
|
case .noWiFi: return NSLocalizedString("You do not appear to be connected to WiFi and/or the WireGuard VPN!\nSideStore will never be able to install or refresh applications without WiFi and the WireGuard VPN.", comment: "")
|
||||||
|
case .tooNewError: return NSLocalizedString("iOS 17 has changed how JIT is enabled therefore SideStore cannot enable it without SideJITServer at this time, sorry for any inconvenience.\nWe will let everyone know once we have a solution!", comment: "")
|
||||||
|
case .unableToConnectSideJIT: return NSLocalizedString("Unable to connect to SideJITServer Please check that you are on the Same Wi-Fi and your Firewall has been set correctly", comment: "")
|
||||||
|
case .unableToRespondSideJITDevice: return NSLocalizedString("SideJITServer is unable to connect to your iDevice Please make sure you have paired your Device by doing 'SideJITServer -y' or try Refreshing SideJITServer from Settings", comment: "")
|
||||||
|
case .wrongSideJITIP: return NSLocalizedString("Incorrect SideJITServer IP Please make sure that you are on the Samw Wifi as SideJITServer", comment: "")
|
||||||
|
case .refreshsidejit: return NSLocalizedString("Unable to find App Please try Refreshing SideJITServer from Settings", comment: "")
|
||||||
|
case .anisetteV1Error: return NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: "")
|
||||||
|
case .provisioningError: return NSLocalizedString("An error occurred when provisioning: %@ %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
||||||
|
case .anisetteV3Error: return NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
||||||
|
case .cacheClearError: return NSLocalizedString("An error occurred while clearing cache: %@", comment: "")
|
||||||
|
case .SideJITIssue: return NSLocalizedString("An error occurred while using SideJIT: %@", comment: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var recoverySuggestion: String? {
|
var recoverySuggestion: String? {
|
||||||
switch self
|
switch self.code
|
||||||
{
|
{
|
||||||
case .maximumAppIDLimitReached(let application, let requiredAppIDs, let availableAppIDs, let date):
|
case .noWiFi: return NSLocalizedString("Make sure the VPN is toggled on and you are connected to any WiFi network!", comment: "")
|
||||||
|
case .maximumAppIDLimitReached:
|
||||||
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
|
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
|
||||||
let message: String
|
guard let appName, let requiredAppIDs, let availableAppIDs, let expirationDate else { return baseMessage }
|
||||||
|
var message: String
|
||||||
|
|
||||||
if requiredAppIDs > 1
|
if requiredAppIDs > 1
|
||||||
{
|
{
|
||||||
let availableText: String
|
let availableText: String
|
||||||
@@ -81,23 +199,23 @@ enum OperationError: LocalizedError
|
|||||||
default: availableText = String(format: NSLocalizedString("only %@ are available", comment: ""), NSNumber(value: availableAppIDs))
|
default: availableText = String(format: NSLocalizedString("only %@ are available", comment: ""), NSNumber(value: availableAppIDs))
|
||||||
}
|
}
|
||||||
|
|
||||||
let prefixMessage = String(format: NSLocalizedString("%@ requires %@ App IDs, but %@.", comment: ""), application.name, NSNumber(value: requiredAppIDs), availableText)
|
let prefixMessage = String(format: NSLocalizedString("%@ requires %@ App IDs, but %@.", comment: ""), appName, NSNumber(value: requiredAppIDs), availableText)
|
||||||
message = prefixMessage + " " + baseMessage
|
message = prefixMessage + " " + baseMessage + "\n\n"
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
let dateComponents = Calendar.current.dateComponents([.day, .hour, .minute], from: Date(), to: date)
|
message = baseMessage + " "
|
||||||
|
|
||||||
let dateComponentsFormatter = DateComponentsFormatter()
|
|
||||||
dateComponentsFormatter.maximumUnitCount = 1
|
|
||||||
dateComponentsFormatter.unitsStyle = .full
|
|
||||||
|
|
||||||
let remainingTime = dateComponentsFormatter.string(from: dateComponents)!
|
|
||||||
|
|
||||||
let remainingTimeMessage = String(format: NSLocalizedString("You can register another App ID in %@.", comment: ""), remainingTime)
|
|
||||||
message = baseMessage + " " + remainingTimeMessage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dateComponents = Calendar.current.dateComponents([.day, .hour, .minute], from: Date(), to: expirationDate)
|
||||||
|
let dateFormatter = DateComponentsFormatter()
|
||||||
|
dateFormatter.maximumUnitCount = 1
|
||||||
|
dateFormatter.unitsStyle = .full
|
||||||
|
|
||||||
|
let remainingTime = dateFormatter.string(from: dateComponents)!
|
||||||
|
|
||||||
|
message += String(format: NSLocalizedString("You can register another App ID in %@.", comment: ""), remainingTime)
|
||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
default: return nil
|
default: return nil
|
||||||
@@ -111,7 +229,7 @@ extension MinimuxerError: LocalizedError {
|
|||||||
case .NoDevice:
|
case .NoDevice:
|
||||||
return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
||||||
case .NoConnection:
|
case .NoConnection:
|
||||||
return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi", comment: "")
|
return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi. This could mean an invalid pairing.", comment: "")
|
||||||
case .PairingFile:
|
case .PairingFile:
|
||||||
return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use jitterbugpair to generate it", comment: "")
|
return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use jitterbugpair to generate it", comment: "")
|
||||||
|
|
||||||
@@ -141,7 +259,7 @@ extension MinimuxerError: LocalizedError {
|
|||||||
case .CreateAfc:
|
case .CreateAfc:
|
||||||
return self.createService(name: "AFC")
|
return self.createService(name: "AFC")
|
||||||
case .RwAfc:
|
case .RwAfc:
|
||||||
return NSLocalizedString("AFC was unable to manage files on the device", comment: "")
|
return NSLocalizedString("AFC was unable to manage files on the device. This usually means an invalid pairing.", comment: "")
|
||||||
case .InstallApp(let message):
|
case .InstallApp(let message):
|
||||||
return NSLocalizedString("Unable to install the app: \(message.toString())", comment: "")
|
return NSLocalizedString("Unable to install the app: \(message.toString())", comment: "")
|
||||||
case .UninstallApp:
|
case .UninstallApp:
|
||||||
|
|||||||
@@ -25,22 +25,38 @@ protocol PatchAppContext
|
|||||||
var error: Error? { get }
|
var error: Error? { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PatchAppError: LocalizedError
|
extension PatchAppError
|
||||||
{
|
{
|
||||||
case unsupportedOperatingSystemVersion(OperatingSystemVersion)
|
enum Code: Int, ALTErrorCode, CaseIterable {
|
||||||
|
typealias Error = PatchAppError
|
||||||
var errorDescription: String? {
|
|
||||||
switch self
|
case unsupportedOperatingSystemVersion
|
||||||
{
|
}
|
||||||
case .unsupportedOperatingSystemVersion(let osVersion):
|
|
||||||
var osVersionString = "\(osVersion.majorVersion).\(osVersion.minorVersion)"
|
static func unsupportedOperatingSystemVersion(_ osVersion: OperatingSystemVersion) -> PatchAppError {
|
||||||
if osVersion.patchVersion != 0
|
PatchAppError(code: .unsupportedOperatingSystemVersion, osVersion: osVersion)
|
||||||
{
|
}
|
||||||
osVersionString += ".\(osVersion.patchVersion)"
|
}
|
||||||
|
|
||||||
|
struct PatchAppError: ALTLocalizedError {
|
||||||
|
let code: Code
|
||||||
|
|
||||||
|
var errorTitle: String?
|
||||||
|
var errorFailure: String?
|
||||||
|
|
||||||
|
var osVersion: OperatingSystemVersion?
|
||||||
|
|
||||||
|
var errorFailureReason: String {
|
||||||
|
switch self.code {
|
||||||
|
case .unsupportedOperatingSystemVersion:
|
||||||
|
let osVersionString: String
|
||||||
|
|
||||||
|
if let osVersion = self.osVersion?.stringValue {
|
||||||
|
osVersionString = NSLocalizedString("iOS", comment: "") + " " + osVersion
|
||||||
|
} else {
|
||||||
|
osVersionString = NSLocalizedString("your device's iOS version", comment: "")
|
||||||
}
|
}
|
||||||
|
return String(format: NSLocalizedString("The OTA download URL for %@ could not be determined.", comment: ""), osVersionString)
|
||||||
let errorDescription = String(format: NSLocalizedString("The OTA download URL for iOS %@ could not be determined.", comment: ""), osVersionString)
|
|
||||||
return errorDescription
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ private extension PatchViewController
|
|||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guard let (bundleIdentifier, result) = results.first else { throw refreshGroup?.context.error ?? OperationError.unknown }
|
guard let (bundleIdentifier, result) = results.first else { throw refreshGroup?.context.error ?? OperationError.unknown() }
|
||||||
_ = try result.get()
|
_ = try result.get()
|
||||||
|
|
||||||
if var patchedApps = UserDefaults.standard.patchedApps, let index = patchedApps.firstIndex(of: bundleIdentifier)
|
if var patchedApps = UserDefaults.standard.patchedApps, let index = patchedApps.firstIndex(of: bundleIdentifier)
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
|
|||||||
if let error = self.context.error { return self.finish(.failure(error)) }
|
if let error = self.context.error { return self.finish(.failure(error)) }
|
||||||
|
|
||||||
guard let profiles = self.context.provisioningProfiles else { return self.finish(.failure(OperationError.invalidParameters)) }
|
guard let profiles = self.context.provisioningProfiles else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||||
guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound)) }
|
guard let app = self.context.app else { return self.finish(.failure(OperationError(.appNotFound(name: nil)))) }
|
||||||
|
|
||||||
for p in profiles {
|
for p in profiles {
|
||||||
do {
|
do {
|
||||||
let bytes = p.value.data.toRustByteSlice()
|
let bytes = p.value.data.toRustByteSlice()
|
||||||
@@ -49,14 +49,13 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
|
|||||||
}
|
}
|
||||||
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
print("Sending refresh app request...")
|
|
||||||
|
|
||||||
self.progress.completedUnitCount += 1
|
self.progress.completedUnitCount += 1
|
||||||
|
|
||||||
let predicate = NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), app.bundleIdentifier)
|
let predicate = NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), app.bundleIdentifier)
|
||||||
self.managedObjectContext.perform {
|
self.managedObjectContext.perform {
|
||||||
guard let installedApp = InstalledApp.first(satisfying: predicate, in: self.managedObjectContext) else {
|
guard let installedApp = InstalledApp.first(satisfying: predicate, in: self.managedObjectContext) else {
|
||||||
self.finish(.failure(OperationError.appNotFound))
|
self.finish(.failure(OperationError(.appNotFound(name: app.name))))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
installedApp.update(provisioningProfile: p.value)
|
installedApp.update(provisioningProfile: p.value)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import Roxas
|
|||||||
|
|
||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
|
import minimuxer
|
||||||
|
|
||||||
@objc(ResignAppOperation)
|
@objc(ResignAppOperation)
|
||||||
final class ResignAppOperation: ResultOperation<ALTApplication>
|
final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||||
@@ -115,7 +116,9 @@ private extension ResignAppOperation
|
|||||||
|
|
||||||
infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
|
infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
|
||||||
infoDictionary[Bundle.Info.altBundleID] = identifier
|
infoDictionary[Bundle.Info.altBundleID] = identifier
|
||||||
infoDictionary[Bundle.Info.devicePairingString] = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String
|
infoDictionary[Bundle.Info.devicePairingString] = "<insert pairing file here>"
|
||||||
|
infoDictionary.removeValue(forKey: "DTXcode")
|
||||||
|
infoDictionary.removeValue(forKey: "DTXcodeBuild")
|
||||||
|
|
||||||
for (key, value) in additionalInfoDictionaryValues
|
for (key, value) in additionalInfoDictionaryValues
|
||||||
{
|
{
|
||||||
@@ -181,9 +184,9 @@ private extension ResignAppOperation
|
|||||||
|
|
||||||
if app.isAltStoreApp
|
if app.isAltStoreApp
|
||||||
{
|
{
|
||||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
guard let udid = fetch_udid()?.toString() as? String else { throw OperationError.unknownUDID }
|
||||||
guard let pairingFileString = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) as? String else { throw OperationError.unknownUDID }
|
guard let pairingFileString = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) as? String else { throw OperationError.unknownUDID }
|
||||||
additionalValues[Bundle.Info.devicePairingString] = pairingFileString
|
additionalValues[Bundle.Info.devicePairingString] = "<insert pairing file here>"
|
||||||
additionalValues[Bundle.Info.deviceID] = udid
|
additionalValues[Bundle.Info.deviceID] = udid
|
||||||
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.preferredServerID
|
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.preferredServerID
|
||||||
|
|
||||||
@@ -202,7 +205,7 @@ private extension ResignAppOperation
|
|||||||
// The embedded certificate + certificate identifier are already in app bundle, no need to update them.
|
// The embedded certificate + certificate identifier are already in app bundle, no need to update them.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String
|
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = fetch_udid()?.toString() as? String
|
||||||
{
|
{
|
||||||
// There is an ALTDeviceID entry, so assume the app is using AltKit and replace it with the device's UDID.
|
// There is an ALTDeviceID entry, so assume the app is using AltKit and replace it with the device's UDID.
|
||||||
additionalValues[Bundle.Info.deviceID] = udid
|
additionalValues[Bundle.Info.deviceID] = udid
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ final class SendAppOperation: ResultOperation<()>
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print("IPA doesn't exist????")
|
print("IPA doesn't exist????")
|
||||||
self.finish(.failure(OperationError.appNotFound))
|
self.finish(.failure(OperationError(.appNotFound(name: resignedApp.name))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,48 +8,87 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
import AltSign
|
import AltSign
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
enum VerificationError: ALTLocalizedError
|
extension VerificationError
|
||||||
{
|
{
|
||||||
case privateEntitlements(ALTApplication, entitlements: [String: Any])
|
enum Code: Int, ALTErrorCode, CaseIterable {
|
||||||
case mismatchedBundleIdentifiers(ALTApplication, sourceBundleID: String)
|
typealias Error = VerificationError
|
||||||
case iOSVersionNotSupported(ALTApplication)
|
|
||||||
|
case privateEntitlements
|
||||||
|
case mismatchedBundleIdentifiers
|
||||||
|
case iOSVersionNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
static func privateEntitlements(_ entitlements: [String: Any], app: ALTApplication) -> VerificationError {
|
||||||
|
VerificationError(code: .privateEntitlements, app: app, entitlements: entitlements)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func mismatchedBundleIdentifiers(sourceBundleID: String, app: ALTApplication) -> VerificationError {
|
||||||
|
VerificationError(code: .mismatchedBundleIdentifiers, app: app, sourceBundleID: sourceBundleID)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func iOSVersionNotSupported(app: AppProtocol, osVersion: OperatingSystemVersion = ProcessInfo.processInfo.operatingSystemVersion, requiredOSVersion: OperatingSystemVersion?) -> VerificationError {
|
||||||
|
VerificationError(code: .iOSVersionNotSupported, app: app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VerificationError: ALTLocalizedError {
|
||||||
|
let code: Code
|
||||||
|
|
||||||
|
var errorTitle: String?
|
||||||
|
var errorFailure: String?
|
||||||
|
|
||||||
|
@Managed var app: AppProtocol?
|
||||||
|
var entitlements: [String: Any]?
|
||||||
|
var sourceBundleID: String?
|
||||||
|
var deviceOSVersion: OperatingSystemVersion?
|
||||||
|
var requiredOSVersion: OperatingSystemVersion?
|
||||||
|
|
||||||
var app: ALTApplication {
|
var errorDescription: String? {
|
||||||
switch self
|
switch self.code {
|
||||||
{
|
case .iOSVersionNotSupported:
|
||||||
case .privateEntitlements(let app, _): return app
|
guard let deviceOSVersion else { return nil }
|
||||||
case .mismatchedBundleIdentifiers(let app, _): return app
|
|
||||||
case .iOSVersionNotSupported(let app): return app
|
var failureReason = self.errorFailureReason
|
||||||
|
if self.app == nil {
|
||||||
|
let firstLetter = failureReason.prefix(1).lowercased()
|
||||||
|
failureReason = firstLetter + failureReason.dropFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(formatted: "This device is running iOS %@, but %@", deviceOSVersion.stringValue, failureReason)
|
||||||
|
default: return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var failure: String? {
|
var errorFailureReason: String {
|
||||||
return String(format: NSLocalizedString("“%@” could not be installed.", comment: ""), app.name)
|
switch self.code
|
||||||
}
|
|
||||||
|
|
||||||
var failureReason: String? {
|
|
||||||
switch self
|
|
||||||
{
|
{
|
||||||
case .privateEntitlements(let app, _):
|
case .privateEntitlements:
|
||||||
return String(format: NSLocalizedString("“%@” requires private permissions.", comment: ""), app.name)
|
let appName = self.$app.name ?? NSLocalizedString("The app", comment: "")
|
||||||
|
return String(formatted: "“%@” requires private permissions.", appName)
|
||||||
case .mismatchedBundleIdentifiers(let app, let sourceBundleID):
|
|
||||||
return String(format: NSLocalizedString("The bundle ID “%@” does not match the one specified by the source (“%@”).", comment: ""), app.bundleIdentifier, sourceBundleID)
|
case .mismatchedBundleIdentifiers:
|
||||||
|
if let appBundleID = self.$app.bundleIdentifier, let bundleID = self.sourceBundleID {
|
||||||
case .iOSVersionNotSupported(let app):
|
return String(formatted: "The bundle ID '%@' does not match the one specified by the source ('%@').", appBundleID, bundleID)
|
||||||
let name = app.name
|
} else {
|
||||||
|
return NSLocalizedString("The bundle ID does not match the one specified by the source.", comment: "")
|
||||||
var version = "iOS \(app.minimumiOSVersion.majorVersion).\(app.minimumiOSVersion.minorVersion)"
|
}
|
||||||
if app.minimumiOSVersion.patchVersion > 0
|
|
||||||
{
|
case .iOSVersionNotSupported:
|
||||||
version += ".\(app.minimumiOSVersion.patchVersion)"
|
let appName = self.$app.name ?? NSLocalizedString("The app", comment: "")
|
||||||
|
let deviceOSVersion = self.deviceOSVersion ?? ProcessInfo.processInfo.operatingSystemVersion
|
||||||
|
|
||||||
|
guard let requiredOSVersion else {
|
||||||
|
return String(formatted: "%@ does not support iOS %@.", appName, deviceOSVersion.stringValue)
|
||||||
|
}
|
||||||
|
if deviceOSVersion > requiredOSVersion {
|
||||||
|
return String(formatted: "%@ requires iOS %@ or earlier", appName, requiredOSVersion.stringValue)
|
||||||
|
} else {
|
||||||
|
return String(formatted: "%@ requires iOS %@ or later", appName, requiredOSVersion.stringValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
let localizedDescription = String(format: NSLocalizedString("%@ requires %@.", comment: ""), name, version)
|
|
||||||
return localizedDescription
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,12 +119,14 @@ final class VerifyAppOperation: ResultOperation<Void>
|
|||||||
|
|
||||||
guard let app = self.context.app else { throw OperationError.invalidParameters }
|
guard let app = self.context.app else { throw OperationError.invalidParameters }
|
||||||
|
|
||||||
guard app.bundleIdentifier == self.context.bundleIdentifier else {
|
if !["ny.litritt.ignited", "com.litritt.ignited"].contains(where: { $0 == app.bundleIdentifier }) {
|
||||||
throw VerificationError.mismatchedBundleIdentifiers(app, sourceBundleID: self.context.bundleIdentifier)
|
guard app.bundleIdentifier == self.context.bundleIdentifier else {
|
||||||
|
throw VerificationError.mismatchedBundleIdentifiers(sourceBundleID: self.context.bundleIdentifier, app: app)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
guard ProcessInfo.processInfo.isOperatingSystemAtLeast(app.minimumiOSVersion) else {
|
guard ProcessInfo.processInfo.isOperatingSystemAtLeast(app.minimumiOSVersion) else {
|
||||||
throw VerificationError.iOSVersionNotSupported(app)
|
throw VerificationError.iOSVersionNotSupported(app: app, requiredOSVersion: app.minimumiOSVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
if #available(iOS 13.5, *)
|
if #available(iOS 13.5, *)
|
||||||
@@ -116,7 +157,7 @@ final class VerifyAppOperation: ResultOperation<Void>
|
|||||||
let entitlements = try PropertyListSerialization.propertyList(from: entitlementsPlist.data(using: .utf8)!, options: [], format: nil) as! [String: Any]
|
let entitlements = try PropertyListSerialization.propertyList(from: entitlementsPlist.data(using: .utf8)!, options: [], format: nil) as! [String: Any]
|
||||||
|
|
||||||
app.hasPrivateEntitlements = true
|
app.hasPrivateEntitlements = true
|
||||||
let error = VerificationError.privateEntitlements(app, entitlements: entitlements)
|
let error = VerificationError.privateEntitlements(entitlements, app: app)
|
||||||
self.process(error) { (result) in
|
self.process(error) { (result) in
|
||||||
self.finish(result.mapError { $0 as Error })
|
self.finish(result.mapError { $0 as Error })
|
||||||
}
|
}
|
||||||
@@ -145,9 +186,10 @@ private extension VerifyAppOperation
|
|||||||
guard let presentingViewController = self.context.presentingViewController else { return completion(.failure(error)) }
|
guard let presentingViewController = self.context.presentingViewController else { return completion(.failure(error)) }
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
switch error
|
switch error.code
|
||||||
{
|
{
|
||||||
case .privateEntitlements(_, let entitlements):
|
case .privateEntitlements:
|
||||||
|
guard let entitlements = error.entitlements else { return completion(.failure(error)) }
|
||||||
let permissions = entitlements.keys.sorted().joined(separator: "\n")
|
let permissions = entitlements.keys.sorted().joined(separator: "\n")
|
||||||
let message = String(format: NSLocalizedString("""
|
let message = String(format: NSLocalizedString("""
|
||||||
You must allow access to these private permissions before continuing:
|
You must allow access to these private permissions before continuing:
|
||||||
@@ -166,8 +208,7 @@ private extension VerifyAppOperation
|
|||||||
}))
|
}))
|
||||||
presentingViewController.present(alertController, animated: true, completion: nil)
|
presentingViewController.present(alertController, animated: true, completion: nil)
|
||||||
|
|
||||||
case .mismatchedBundleIdentifiers: return completion(.failure(error))
|
case .mismatchedBundleIdentifiers, .iOSVersionNotSupported: return completion(.failure(error))
|
||||||
case .iOSVersionNotSupported: return completion(.failure(error))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +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>PSMultiValueSpecifier</string>
|
|
||||||
<key>Title</key>
|
|
||||||
<string>Anisette Server</string>
|
|
||||||
<key>Key</key>
|
|
||||||
<string>customAnisetteURL</string>
|
|
||||||
<key>DefaultValue</key>
|
|
||||||
<string>https://ani.sidestore.io</string>
|
|
||||||
<key>Titles</key>
|
|
||||||
<array>
|
|
||||||
<string>SideStore</string>
|
|
||||||
<string>Macley (US)</string>
|
|
||||||
<string>Macley (DE)</string>
|
|
||||||
<string>DrPudding</string>
|
|
||||||
<string>Sideloadly</string>
|
|
||||||
<string>Nick</string>
|
|
||||||
<string>Jawshoeadan</string>
|
|
||||||
<string>crystall1nedev</string>
|
|
||||||
</array>
|
|
||||||
<key>Values</key>
|
|
||||||
<array>
|
|
||||||
<string>https://ani.sidestore.io</string>
|
|
||||||
<string>http://5.249.163.88:6969/</string>
|
|
||||||
<string>http://45.132.246.138:6969/</string>
|
|
||||||
<string>https://sign.rheaa.xyz</string>
|
|
||||||
<string>https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx</string>
|
|
||||||
<string>http://45.33.29.114</string>
|
|
||||||
<string>https://anisette.jawshoeadan.me</string>
|
|
||||||
<string>https://anisette.crystall1ne.software/</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>Type</key>
|
|
||||||
<string>PSGroupSpecifier</string>
|
|
||||||
<key>Title</key>
|
|
||||||
<string>Danger Zone</string>
|
|
||||||
<key>FooterText</key>
|
|
||||||
<string>If you disable the toggle then app will use the server you input into the "Anisette URL" box rather than one selected from the above selector.</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>Type</key>
|
|
||||||
<string>PSToggleSwitchSpecifier</string>
|
|
||||||
<key>Title</key>
|
|
||||||
<string>Use preferred servers</string>
|
|
||||||
<key>Key</key>
|
|
||||||
<string>textServer</string>
|
|
||||||
<key>DefaultValue</key>
|
|
||||||
<true/>
|
|
||||||
<key>FooterText</key>
|
|
||||||
<string>chicken</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
|
||||||
<key>Type</key>
|
|
||||||
<string>PSTextFieldSpecifier</string>
|
|
||||||
<key>Title</key>
|
|
||||||
<string>Anisette URL</string>
|
|
||||||
<key>Key</key>
|
|
||||||
<string>textInputAnisetteURL</string>
|
|
||||||
<key>AutocapitalizationType</key>
|
|
||||||
<string>None</string>
|
|
||||||
<key>AutocorrectionType</key>
|
|
||||||
<string>No</string>
|
|
||||||
<key>KeyboardType</key>
|
|
||||||
<string>URL</string>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
Binary file not shown.
@@ -10,6 +10,11 @@ import Foundation
|
|||||||
|
|
||||||
public struct AnisetteManager {
|
public struct AnisetteManager {
|
||||||
|
|
||||||
|
var menuURL: String {
|
||||||
|
var url: String
|
||||||
|
url = UserDefaults.standard.menuAnisetteURL
|
||||||
|
return url
|
||||||
|
}
|
||||||
/// User defined URL from Settings/UserDefaults
|
/// User defined URL from Settings/UserDefaults
|
||||||
static var userURL: String? {
|
static var userURL: String? {
|
||||||
var urlString: String?
|
var urlString: String?
|
||||||
|
|||||||
190
AltStore/Settings/AnisetteServerList.swift
Normal file
190
AltStore/Settings/AnisetteServerList.swift
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
//
|
||||||
|
// AnisetteServerList.swift
|
||||||
|
// SideStore
|
||||||
|
//
|
||||||
|
// Created by ny on 6/18/24.
|
||||||
|
// Copyright © 2024 SideStore. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
|
typealias SUIButton = SwiftUI.Button
|
||||||
|
|
||||||
|
// MARK: - AnisetteServerData
|
||||||
|
struct AnisetteServerData: Codable {
|
||||||
|
let servers: [Server]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Server
|
||||||
|
struct Server: Codable {
|
||||||
|
var name: String
|
||||||
|
var address: String
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnisetteViewModel: ObservableObject {
|
||||||
|
@Published var selected: String = ""
|
||||||
|
|
||||||
|
@Published var source: String = "https://servers.sidestore.io/servers.json"
|
||||||
|
@Published var servers: [Server] = []
|
||||||
|
|
||||||
|
func getListOfServers() {
|
||||||
|
guard let url = URL(string: source) else { return }
|
||||||
|
URLSession.shared.dataTask(with: url) { data, response, error in
|
||||||
|
if let error = error {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let data = data {
|
||||||
|
do {
|
||||||
|
let decoder = Foundation.JSONDecoder()
|
||||||
|
let servers = try decoder.decode(AnisetteServerData.self, from: data)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.servers = servers.servers
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Handle decoding error
|
||||||
|
print("Failed to decode JSON: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AnisetteServers: View {
|
||||||
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
@StateObject var viewModel: AnisetteViewModel = AnisetteViewModel()
|
||||||
|
@State var selected: String? = nil
|
||||||
|
var errorCallback: () -> ()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
Color(UIColor.systemBackground)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
.onAppear {
|
||||||
|
viewModel.getListOfServers()
|
||||||
|
}
|
||||||
|
VStack {
|
||||||
|
if #available(iOS 16.0, *) {
|
||||||
|
SwiftUI.List($viewModel.servers, id: \.address, selection: $selected) { server in
|
||||||
|
HStack {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("\(server.name.wrappedValue)")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
Text("\(server.address.wrappedValue)")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
if selected != nil {
|
||||||
|
if server.address.wrappedValue == selected {
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
.onAppear {
|
||||||
|
UserDefaults.standard.menuAnisetteURL = server.address.wrappedValue
|
||||||
|
print(UserDefaults.synchronize(.standard)())
|
||||||
|
print(UserDefaults.standard.menuAnisetteURL)
|
||||||
|
print(server.address.wrappedValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(RoundedRectangle(cornerRadius: 10).fill(Color(UIColor.secondarySystemBackground)))
|
||||||
|
.shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 5)
|
||||||
|
}
|
||||||
|
.listStyle(.plain)
|
||||||
|
.scrollContentBackground(.hidden)
|
||||||
|
.listRowBackground(Color(UIColor.systemBackground))
|
||||||
|
} else {
|
||||||
|
List(selection: $selected) {
|
||||||
|
ForEach($viewModel.servers, id: \.name) { server in
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
Text("\(server.name.wrappedValue)")
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.frame(alignment: .center)
|
||||||
|
Text("\(server.address.wrappedValue)")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.frame(alignment: .center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(RoundedRectangle(cornerRadius: 10).fill(Color(UIColor.secondarySystemBackground)))
|
||||||
|
.shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 5)
|
||||||
|
}
|
||||||
|
.listStyle(.plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
TextField("Anisette Server List", text: $viewModel.source)
|
||||||
|
.padding()
|
||||||
|
.background(RoundedRectangle(cornerRadius: 10).fill(Color(UIColor.secondarySystemFill)))
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.frame(height: 60)
|
||||||
|
.shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 5)
|
||||||
|
.onChange(of: viewModel.source) { newValue in
|
||||||
|
UserDefaults.standard.menuAnisetteList = newValue
|
||||||
|
viewModel.getListOfServers()
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
SUIButton(action: {
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
}) {
|
||||||
|
Text("Back")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.padding()
|
||||||
|
.background(RoundedRectangle(cornerRadius: 10).fill(Color.accentColor))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.shadow(color: Color.accentColor.opacity(0.4), radius: 10, x: 0, y: 5)
|
||||||
|
|
||||||
|
SUIButton(action: {
|
||||||
|
viewModel.getListOfServers()
|
||||||
|
}) {
|
||||||
|
Text("Refresh Servers")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.padding()
|
||||||
|
.background(RoundedRectangle(cornerRadius: 10).fill(Color.accentColor))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.shadow(color: Color.accentColor.opacity(0.4), radius: 10, x: 0, y: 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
SUIButton(action: {
|
||||||
|
#if !DEBUG
|
||||||
|
if Keychain.shared.adiPb != nil {
|
||||||
|
Keychain.shared.adiPb = nil
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
print("Cleared adi.pb from keychain")
|
||||||
|
errorCallback()
|
||||||
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
}) {
|
||||||
|
Text("Reset adi.pb")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.padding()
|
||||||
|
.background(RoundedRectangle(cornerRadius: 10).fill(Color.red))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.shadow(color: Color.red.opacity(0.4), radius: 10, x: 0, y: 5)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.padding(.bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationBarHidden(true)
|
||||||
|
.navigationTitle("")
|
||||||
|
}
|
||||||
|
}
|
||||||
53
AltStore/Settings/Error Log/ErrorDetailsViewController.swift
Normal file
53
AltStore/Settings/Error Log/ErrorDetailsViewController.swift
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// ErrorDetailsViewController.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 10/5/22.
|
||||||
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
|
class ErrorDetailsViewController: UIViewController
|
||||||
|
{
|
||||||
|
var loggedError: LoggedError?
|
||||||
|
|
||||||
|
@IBOutlet private var textView: UITextView!
|
||||||
|
|
||||||
|
override func viewDidLoad()
|
||||||
|
{
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
if let error = self.loggedError?.error
|
||||||
|
{
|
||||||
|
self.title = error.localizedErrorCode
|
||||||
|
|
||||||
|
let font = self.textView.font ?? UIFont.preferredFont(forTextStyle: .body)
|
||||||
|
let detailedDescription = error.formattedDetailedDescription(with: font)
|
||||||
|
self.textView.attributedText = detailedDescription
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.title = NSLocalizedString("Error Details", comment: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
self.navigationController?.navigationBar.tintColor = .altPrimary
|
||||||
|
|
||||||
|
if #available(iOS 15, *), let sheetController = self.navigationController?.sheetPresentationController
|
||||||
|
{
|
||||||
|
sheetController.detents = [.medium(), .large()]
|
||||||
|
sheetController.selectedDetentIdentifier = .medium
|
||||||
|
sheetController.prefersGrabberVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLayoutSubviews()
|
||||||
|
{
|
||||||
|
super.viewDidLayoutSubviews()
|
||||||
|
|
||||||
|
self.textView.textContainerInset.left = self.view.layoutMargins.left
|
||||||
|
self.textView.textContainerInset.right = self.view.layoutMargins.right
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,16 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
@objc(ErrorLogMenuButton)
|
||||||
|
private final class ErrorLogMenuButton: UIButton {
|
||||||
|
@available(iOS 14.0, *)
|
||||||
|
override func menuAttachmentPoint(for configuration: UIContextMenuConfiguration) -> CGPoint {
|
||||||
|
var point = super.menuAttachmentPoint(for: configuration)
|
||||||
|
point.y = self.bounds.midY
|
||||||
|
return point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc(ErrorLogTableViewCell)
|
@objc(ErrorLogTableViewCell)
|
||||||
final class ErrorLogTableViewCell: UITableViewCell
|
final class ErrorLogTableViewCell: UITableViewCell
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,6 +21,13 @@ final class ErrorLogViewController: UITableViewController
|
|||||||
private lazy var dataSource = self.makeDataSource()
|
private lazy var dataSource = self.makeDataSource()
|
||||||
private var expandedErrorIDs = Set<NSManagedObjectID>()
|
private var expandedErrorIDs = Set<NSManagedObjectID>()
|
||||||
|
|
||||||
|
private var isScrolling = false {
|
||||||
|
didSet {
|
||||||
|
guard self.isScrolling != oldValue else { return }
|
||||||
|
self.updateButtonInteractivity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private lazy var timeFormatter: DateFormatter = {
|
private lazy var timeFormatter: DateFormatter = {
|
||||||
let dateFormatter = DateFormatter()
|
let dateFormatter = DateFormatter()
|
||||||
dateFormatter.dateStyle = .none
|
dateFormatter.dateStyle = .none
|
||||||
@@ -39,6 +46,15 @@ final class ErrorLogViewController: UITableViewController
|
|||||||
self.tableView.dataSource = self.dataSource
|
self.tableView.dataSource = self.dataSource
|
||||||
self.tableView.prefetchDataSource = self.dataSource
|
self.tableView.prefetchDataSource = self.dataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
|
guard let loggedError = sender as? LoggedError, segue.identifier == "showErrorDetails" else { return }
|
||||||
|
|
||||||
|
let navigationController = segue.destination as! UINavigationController
|
||||||
|
|
||||||
|
let errorDetailsViewController = navigationController.viewControllers.first as! ErrorDetailsViewController
|
||||||
|
errorDetailsViewController.loggedError = loggedError
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension ErrorLogViewController
|
private extension ErrorLogViewController
|
||||||
@@ -60,14 +76,8 @@ private extension ErrorLogViewController
|
|||||||
let cell = cell as! ErrorLogTableViewCell
|
let cell = cell as! ErrorLogTableViewCell
|
||||||
cell.dateLabel.text = self.timeFormatter.string(from: loggedError.date)
|
cell.dateLabel.text = self.timeFormatter.string(from: loggedError.date)
|
||||||
cell.errorFailureLabel.text = loggedError.localizedFailure ?? NSLocalizedString("Operation Failed", comment: "")
|
cell.errorFailureLabel.text = loggedError.localizedFailure ?? NSLocalizedString("Operation Failed", comment: "")
|
||||||
|
cell.errorCodeLabel.text = loggedError.error.localizedErrorCode
|
||||||
switch loggedError.domain
|
|
||||||
{
|
|
||||||
case AltServerErrorDomain: cell.errorCodeLabel?.text = String(format: NSLocalizedString("AltServer Error %@", comment: ""), NSNumber(value: loggedError.code))
|
|
||||||
case OperationError.domain: cell.errorCodeLabel?.text = String(format: NSLocalizedString("AltStore Error %@", comment: ""), NSNumber(value: loggedError.code))
|
|
||||||
default: cell.errorCodeLabel?.text = loggedError.error.localizedErrorCode
|
|
||||||
}
|
|
||||||
|
|
||||||
let nsError = loggedError.error as NSError
|
let nsError = loggedError.error as NSError
|
||||||
let errorDescription = [nsError.localizedDescription, nsError.localizedRecoverySuggestion].compactMap { $0 }.joined(separator: "\n\n")
|
let errorDescription = [nsError.localizedDescription, nsError.localizedRecoverySuggestion].compactMap { $0 }.joined(separator: "\n\n")
|
||||||
cell.errorDescriptionTextView.text = errorDescription
|
cell.errorDescriptionTextView.text = errorDescription
|
||||||
@@ -93,12 +103,19 @@ private extension ErrorLogViewController
|
|||||||
},
|
},
|
||||||
UIAction(title: NSLocalizedString("Search FAQ", comment: ""), image: UIImage(systemName: "magnifyingglass")) { [weak self] _ in
|
UIAction(title: NSLocalizedString("Search FAQ", comment: ""), image: UIImage(systemName: "magnifyingglass")) { [weak self] _ in
|
||||||
self?.searchFAQ(for: loggedError)
|
self?.searchFAQ(for: loggedError)
|
||||||
|
},
|
||||||
|
UIAction(title: NSLocalizedString("View More Details", comment: ""), image: UIImage(systemName: "ellipsis.circle")) { [weak self] _ in
|
||||||
|
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
cell.menuButton.menu = menu
|
cell.menuButton.menu = menu
|
||||||
|
cell.menuButton.showsMenuAsPrimaryAction = self.isScrolling ? false : true
|
||||||
|
cell.selectionStyle = .none
|
||||||
|
} else {
|
||||||
|
cell.menuButton.isUserInteractionEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include errorDescriptionTextView's text in cell summary.
|
// Include errorDescriptionTextView's text in cell summary.
|
||||||
cell.accessibilityLabel = [cell.errorFailureLabel.text, cell.dateLabel.text, cell.errorCodeLabel.text, cell.errorDescriptionTextView.text].compactMap { $0 }.joined(separator: ". ")
|
cell.accessibilityLabel = [cell.errorFailureLabel.text, cell.dateLabel.text, cell.errorCodeLabel.text, cell.errorDescriptionTextView.text].compactMap { $0 }.joined(separator: ". ")
|
||||||
|
|
||||||
@@ -232,22 +249,27 @@ private extension ErrorLogViewController
|
|||||||
|
|
||||||
func searchFAQ(for loggedError: LoggedError)
|
func searchFAQ(for loggedError: LoggedError)
|
||||||
{
|
{
|
||||||
let baseURL = URL(string: "https://faq.altstore.io/getting-started/troubleshooting-guide")!
|
let baseURL = URL(string: "https://faq.altstore.io/getting-started/error-codes")!
|
||||||
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)!
|
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)!
|
||||||
|
|
||||||
let query = [loggedError.domain, "\(loggedError.code)"].joined(separator: "+")
|
let query = [loggedError.domain, "\(loggedError.error.displayCode)"].joined(separator: "+")
|
||||||
components.queryItems = [URLQueryItem(name: "q", value: query)]
|
components.queryItems = [URLQueryItem(name: "q", value: query)]
|
||||||
|
|
||||||
let safariViewController = SFSafariViewController(url: components.url ?? baseURL)
|
let safariViewController = SFSafariViewController(url: components.url ?? baseURL)
|
||||||
safariViewController.preferredControlTintColor = .altPrimary
|
safariViewController.preferredControlTintColor = .altPrimary
|
||||||
self.present(safariViewController, animated: true)
|
self.present(safariViewController, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func viewMoreDetails(for loggedError: LoggedError) {
|
||||||
|
self.performSegue(withIdentifier: "showErrorDetails", sender: loggedError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ErrorLogViewController
|
extension ErrorLogViewController
|
||||||
{
|
{
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
|
||||||
{
|
{
|
||||||
|
guard #unavailable(iOS 14) else { return }
|
||||||
let loggedError = self.dataSource.item(at: indexPath)
|
let loggedError = self.dataSource.item(at: indexPath)
|
||||||
|
|
||||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||||
@@ -321,3 +343,32 @@ extension ErrorLogViewController: QLPreviewControllerDataSource {
|
|||||||
return fileURL as QLPreviewItem
|
return fileURL as QLPreviewItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ErrorLogViewController
|
||||||
|
{
|
||||||
|
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView)
|
||||||
|
{
|
||||||
|
self.isScrolling = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
|
||||||
|
{
|
||||||
|
self.isScrolling = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool)
|
||||||
|
{
|
||||||
|
guard !decelerate else { return }
|
||||||
|
self.isScrolling = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateButtonInteractivity()
|
||||||
|
{
|
||||||
|
guard #available(iOS 14, *) else { return }
|
||||||
|
|
||||||
|
for case let cell as ErrorLogTableViewCell in self.tableView.visibleCells
|
||||||
|
{
|
||||||
|
cell.menuButton.showsMenuAsPrimaryAction = self.isScrolling ? false : true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="5Rz-4h-jJ8">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23090" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="5Rz-4h-jJ8">
|
||||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23079"/>
|
||||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
@@ -20,8 +20,8 @@
|
|||||||
<color key="backgroundColor" name="SettingsBackground"/>
|
<color key="backgroundColor" name="SettingsBackground"/>
|
||||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
||||||
<rect key="frame" x="0.0" y="1126" width="375" height="25"/>
|
<rect key="frame" x="0.0" y="1296" width="375" height="25"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
@@ -236,15 +236,51 @@
|
|||||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
|
||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="amC-sE-8O0" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="GYp-O0-pse" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="444" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="444" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="GYp-O0-pse" id="vDG-ZV-xRS">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Disable Idle Timeout" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PCh-Up-aJJ">
|
||||||
|
<rect key="frame" x="30" y="15.5" width="166" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iQA-wm-5ag">
|
||||||
|
<rect key="frame" x="296" y="10" width="51" height="31"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="toggleNoIdleTimeoutEnabled:" destination="aMk-Xp-UL8" eventType="valueChanged" id="WSl-Jc-g5J"/>
|
||||||
|
</connections>
|
||||||
|
</switch>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="iQA-wm-5ag" secondAttribute="trailing" id="MJ1-HF-Dln"/>
|
||||||
|
<constraint firstItem="PCh-Up-aJJ" firstAttribute="leading" secondItem="vDG-ZV-xRS" secondAttribute="leadingMargin" id="Ocu-jn-RAQ"/>
|
||||||
|
<constraint firstItem="iQA-wm-5ag" firstAttribute="centerY" secondItem="vDG-ZV-xRS" secondAttribute="centerY" id="c6W-bN-VAb"/>
|
||||||
|
<constraint firstItem="PCh-Up-aJJ" firstAttribute="centerY" secondItem="vDG-ZV-xRS" secondAttribute="centerY" id="mL6-LB-cjn"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||||
|
<integer key="value" value="2"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
</tableViewCell>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="amC-sE-8O0" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="495" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="amC-sE-8O0" id="GEO-2e-E4k">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="amC-sE-8O0" id="GEO-2e-E4k">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Add to Siri…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6K-fI-CVr">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Allow Siri To Refresh Apps…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6K-fI-CVr">
|
||||||
<rect key="frame" x="30" y="15.5" width="100.5" height="20.5"/>
|
<rect key="frame" x="30" y="15.5" width="228.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -269,7 +305,7 @@
|
|||||||
<tableViewSection headerTitle="" id="eHy-qI-w5w">
|
<tableViewSection headerTitle="" id="eHy-qI-w5w">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="30h-59-88f" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="30h-59-88f" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="535" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="586" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="30h-59-88f" id="7qD-DW-Jls">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="30h-59-88f" id="7qD-DW-Jls">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -309,7 +345,7 @@
|
|||||||
<tableViewSection headerTitle="" id="J90-vn-u2O">
|
<tableViewSection headerTitle="" id="J90-vn-u2O">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="626" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="677" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="i4T-2q-jF3" id="VTQ-H4-aCM">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="i4T-2q-jF3" id="VTQ-H4-aCM">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -353,7 +389,7 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="oHX-oR-nwJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="oHX-oR-nwJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="677" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="728" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oHX-oR-nwJ" id="hN4-i5-igu">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oHX-oR-nwJ" id="hN4-i5-igu">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -397,7 +433,7 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="728" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="779" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0MT-ht-Sit" id="OZp-WM-5H7">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0MT-ht-Sit" id="OZp-WM-5H7">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -441,7 +477,7 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="779" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="830" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -481,7 +517,7 @@
|
|||||||
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
||||||
<cells>
|
<cells>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="870" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="921" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -514,7 +550,7 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="921" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="972" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -550,7 +586,7 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="rE2-P4-OaE" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="rE2-P4-OaE" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="972" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1023" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rE2-P4-OaE" id="qIT-rz-ZUb">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rE2-P4-OaE" id="qIT-rz-ZUb">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -582,11 +618,77 @@
|
|||||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
<connections>
|
<connections>
|
||||||
<segue destination="g8a-Rf-zWa" kind="show" identifier="showErrorLog" id="SSW-vL-86I"/>
|
<segue destination="g8a-Rf-zWa" kind="show" identifier="showErrorLog" id="vFC-Id-Ww6"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VrV-qI-zXF" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VrV-qI-zXF" id="w1r-uY-4pD">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="SideJITServer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="46q-DB-5nc">
|
||||||
|
<rect key="frame" x="30" y="15.5" width="183" height="21"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="wvD-eZ-nQI">
|
||||||
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="wvD-eZ-nQI" firstAttribute="centerY" secondItem="w1r-uY-4pD" secondAttribute="centerY" id="O6Y-Y1-yxv"/>
|
||||||
|
<constraint firstItem="46q-DB-5nc" firstAttribute="centerY" secondItem="w1r-uY-4pD" secondAttribute="centerY" id="ROS-YF-6jb"/>
|
||||||
|
<constraint firstItem="46q-DB-5nc" firstAttribute="leading" secondItem="w1r-uY-4pD" secondAttribute="leadingMargin" id="acd-O8-WTI"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="wvD-eZ-nQI" secondAttribute="trailing" id="taB-EP-QMM"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||||
|
<integer key="value" value="2"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
</tableViewCell>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="eZ3-BT-q4D" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="1125" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="eZ3-BT-q4D" id="17m-VV-hzf">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Clear Cache" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IbH-V1-ce3">
|
||||||
|
<rect key="frame" x="30" y="15.5" width="98.5" height="20.5"/>
|
||||||
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="FZe-BJ-fOm">
|
||||||
|
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="FZe-BJ-fOm" firstAttribute="centerY" secondItem="17m-VV-hzf" secondAttribute="centerY" id="bGv-Np-5aO"/>
|
||||||
|
<constraint firstAttribute="trailingMargin" secondItem="FZe-BJ-fOm" secondAttribute="trailing" id="ccb-JP-Eqi"/>
|
||||||
|
<constraint firstItem="IbH-V1-ce3" firstAttribute="centerY" secondItem="17m-VV-hzf" secondAttribute="centerY" id="iQJ-gN-sRF"/>
|
||||||
|
<constraint firstItem="IbH-V1-ce3" firstAttribute="leading" secondItem="17m-VV-hzf" secondAttribute="leadingMargin" id="m1g-Y6-aT5"/>
|
||||||
|
</constraints>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||||
|
<integer key="value" value="2"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="1023" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1176" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
@@ -619,14 +721,14 @@
|
|||||||
</userDefinedRuntimeAttributes>
|
</userDefinedRuntimeAttributes>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="1227" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Reset adi.pb" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eds-Dj-36y">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Anisette Servers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eds-Dj-36y">
|
||||||
<rect key="frame" x="30" y="15.5" width="102" height="20.5"/>
|
<rect key="frame" x="30" y="15.5" width="135.5" height="20.5"/>
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@@ -644,39 +746,6 @@
|
|||||||
</tableViewCellContentView>
|
</tableViewCellContentView>
|
||||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||||
<userDefinedRuntimeAttributes>
|
|
||||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
|
||||||
<integer key="value" value="2"/>
|
|
||||||
</userDefinedRuntimeAttribute>
|
|
||||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
|
||||||
</userDefinedRuntimeAttributes>
|
|
||||||
</tableViewCell>
|
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
|
||||||
<rect key="frame" x="0.0" y="1125" width="375" height="51"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fj2-EJ-Z98" id="BcT-Fs-KNg">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<subviews>
|
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Advanced Settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OcM-OM-uDE">
|
|
||||||
<rect key="frame" x="30" y="15.5" width="154" height="20.5"/>
|
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Pcu-Sy-yfZ">
|
|
||||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
|
||||||
</imageView>
|
|
||||||
</subviews>
|
|
||||||
<constraints>
|
|
||||||
<constraint firstAttribute="trailingMargin" secondItem="Pcu-Sy-yfZ" secondAttribute="trailing" id="CFy-IO-4eb"/>
|
|
||||||
<constraint firstItem="OcM-OM-uDE" firstAttribute="centerY" secondItem="BcT-Fs-KNg" secondAttribute="centerY" id="OGl-h4-FPx"/>
|
|
||||||
<constraint firstItem="Pcu-Sy-yfZ" firstAttribute="centerY" secondItem="BcT-Fs-KNg" secondAttribute="centerY" id="R7L-4O-lTn"/>
|
|
||||||
<constraint firstItem="OcM-OM-uDE" firstAttribute="leading" secondItem="BcT-Fs-KNg" secondAttribute="leadingMargin" id="yoh-C6-UC5"/>
|
|
||||||
</constraints>
|
|
||||||
</tableViewCellContentView>
|
|
||||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
|
||||||
<userDefinedRuntimeAttributes>
|
<userDefinedRuntimeAttributes>
|
||||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||||
<integer key="value" value="3"/>
|
<integer key="value" value="3"/>
|
||||||
@@ -689,7 +758,6 @@
|
|||||||
</sections>
|
</sections>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="dataSource" destination="aMk-Xp-UL8" id="c6c-fR-8C4"/>
|
<outlet property="dataSource" destination="aMk-Xp-UL8" id="c6c-fR-8C4"/>
|
||||||
<outlet property="delegate" destination="aMk-Xp-UL8" id="moP-1B-lRq"/>
|
|
||||||
</connections>
|
</connections>
|
||||||
</tableView>
|
</tableView>
|
||||||
<navigationItem key="navigationItem" title="Settings" id="Ddg-UQ-KJ8"/>
|
<navigationItem key="navigationItem" title="Settings" id="Ddg-UQ-KJ8"/>
|
||||||
@@ -698,6 +766,7 @@
|
|||||||
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
|
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
|
||||||
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
|
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
|
||||||
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
|
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
|
||||||
|
<outlet property="noIdleTimeoutSwitch" destination="iQA-wm-5ag" id="jHC-js-q0Y"/>
|
||||||
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
|
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
|
||||||
</connections>
|
</connections>
|
||||||
</tableViewController>
|
</tableViewController>
|
||||||
@@ -713,7 +782,7 @@
|
|||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
|
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Jtn-cs-Tvp" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Jtn-cs-Tvp" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<color key="barTintColor" name="SettingsBackground"/>
|
<color key="barTintColor" name="SettingsBackground"/>
|
||||||
@@ -814,7 +883,7 @@
|
|||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" layoutMarginsFollowReadableWidth="YES" contentInsetAdjustmentBehavior="never" indicatorStyle="white" editable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oQQ-pR-oKc">
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" layoutMarginsFollowReadableWidth="YES" contentInsetAdjustmentBehavior="never" indicatorStyle="white" editable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oQQ-pR-oKc">
|
||||||
<rect key="frame" x="0.0" y="44" width="375" height="574"/>
|
<rect key="frame" x="0.0" y="64" width="375" height="554"/>
|
||||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||||
<string key="text">Jay Freeman (ldid)
|
<string key="text">Jay Freeman (ldid)
|
||||||
Copyright (C) 2007-2012 Jay Freeman (saurik)
|
Copyright (C) 2007-2012 Jay Freeman (saurik)
|
||||||
@@ -1048,7 +1117,7 @@ Settings by i cons from the Noun Project</string>
|
|||||||
</textView>
|
</textView>
|
||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<button opaque="NO" contentMode="scaleToFill" showsMenuAsPrimaryAction="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ba2-EY-tf5">
|
<button opaque="NO" contentMode="scaleToFill" showsMenuAsPrimaryAction="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ba2-EY-tf5" customClass="ErrorLogMenuButton">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="107.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="343" height="107.5"/>
|
||||||
<accessibility key="accessibilityConfiguration">
|
<accessibility key="accessibilityConfiguration">
|
||||||
<bool key="isElement" value="NO"/>
|
<bool key="isElement" value="NO"/>
|
||||||
@@ -1097,11 +1166,73 @@ Settings by i cons from the Noun Project</string>
|
|||||||
</barButtonItem>
|
</barButtonItem>
|
||||||
</rightBarButtonItems>
|
</rightBarButtonItems>
|
||||||
</navigationItem>
|
</navigationItem>
|
||||||
|
<connections>
|
||||||
|
<segue destination="7gm-d1-zWK" kind="presentation" identifier="showErrorDetails" id="9vz-y6-evp"/>
|
||||||
|
</connections>
|
||||||
</tableViewController>
|
</tableViewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="rU1-TZ-TD8" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="rU1-TZ-TD8" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="1697" y="1774"/>
|
<point key="canvasLocation" x="1697" y="1774"/>
|
||||||
</scene>
|
</scene>
|
||||||
|
<!--Error Details View Controller-->
|
||||||
|
<scene sceneID="XNO-Yg-I7t">
|
||||||
|
<objects>
|
||||||
|
<viewController id="xB2-Se-VVg" customClass="ErrorDetailsViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<view key="view" contentMode="scaleToFill" id="eBQ-se-VIy">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="ctd-NB-4ov">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
<color key="textColor" systemColor="labelColor"/>
|
||||||
|
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||||
|
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||||
|
</textView>
|
||||||
|
</subviews>
|
||||||
|
<viewLayoutGuide key="safeArea" id="Nm8-69-Ngi"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="ctd-NB-4ov" firstAttribute="leading" secondItem="eBQ-se-VIy" secondAttribute="leading" id="Cv1-Te-gBH"/>
|
||||||
|
<constraint firstItem="ctd-NB-4ov" firstAttribute="top" secondItem="eBQ-se-VIy" secondAttribute="top" id="HRY-Rg-iMI"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="ctd-NB-4ov" secondAttribute="trailing" id="Lc1-K7-iuq"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="ctd-NB-4ov" secondAttribute="bottom" id="zCz-Cy-Y5z"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<navigationItem key="navigationItem" id="XpE-V9-EaY">
|
||||||
|
<barButtonItem key="rightBarButtonItem" style="done" systemItem="done" id="rnr-dX-4Ev">
|
||||||
|
<connections>
|
||||||
|
<segue destination="ZSp-1n-UJ9" kind="unwind" unwindAction="unwindFromErrorDetails:" id="TFu-zD-QyF"/>
|
||||||
|
</connections>
|
||||||
|
</barButtonItem>
|
||||||
|
</navigationItem>
|
||||||
|
<connections>
|
||||||
|
<outlet property="textView" destination="ctd-NB-4ov" id="x2C-9R-Xz1"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="8AM-Vx-XTN" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
|
<exit id="ZSp-1n-UJ9" userLabel="Exit" sceneMemberID="exit"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="3389.5999999999999" y="1772.5637181409297"/>
|
||||||
|
</scene>
|
||||||
|
<!--Navigation Controller-->
|
||||||
|
<scene sceneID="4LJ-Od-dCK">
|
||||||
|
<objects>
|
||||||
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="7gm-d1-zWK" sceneMemberID="viewController">
|
||||||
|
<toolbarItems/>
|
||||||
|
<navigationBar key="navigationBar" contentMode="scaleToFill" id="dI0-sh-yGf">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="56"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
</navigationBar>
|
||||||
|
<nil name="viewControllers"/>
|
||||||
|
<connections>
|
||||||
|
<segue destination="xB2-Se-VVg" kind="relationship" relationship="rootViewController" id="RpP-UM-JfJ"/>
|
||||||
|
</connections>
|
||||||
|
</navigationController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="OXW-bf-HIj" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="2554" y="1773"/>
|
||||||
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="Next" width="18" height="18"/>
|
<image name="Next" width="18" height="18"/>
|
||||||
@@ -1114,7 +1245,10 @@ Settings by i cons from the Noun Project</string>
|
|||||||
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
</systemColor>
|
</systemColor>
|
||||||
<systemColor name="labelColor">
|
<systemColor name="labelColor">
|
||||||
<color red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</systemColor>
|
||||||
|
<systemColor name="systemBackgroundColor">
|
||||||
|
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
</systemColor>
|
</systemColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
import SafariServices
|
import SafariServices
|
||||||
import MessageUI
|
import MessageUI
|
||||||
import Intents
|
import Intents
|
||||||
@@ -30,13 +31,14 @@ extension SettingsViewController
|
|||||||
fileprivate enum AppRefreshRow: Int, CaseIterable
|
fileprivate enum AppRefreshRow: Int, CaseIterable
|
||||||
{
|
{
|
||||||
case backgroundRefresh
|
case backgroundRefresh
|
||||||
|
case noIdleTimeout
|
||||||
|
|
||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
case addToSiri
|
case addToSiri
|
||||||
|
|
||||||
static var allCases: [AppRefreshRow] {
|
static var allCases: [AppRefreshRow] {
|
||||||
guard #available(iOS 14, *) else { return [.backgroundRefresh] }
|
guard #available(iOS 14, *) else { return [.backgroundRefresh, .noIdleTimeout] }
|
||||||
return [.backgroundRefresh, .addToSiri]
|
return [.backgroundRefresh, .noIdleTimeout, .addToSiri]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,9 +55,12 @@ extension SettingsViewController
|
|||||||
case sendFeedback
|
case sendFeedback
|
||||||
case refreshAttempts
|
case refreshAttempts
|
||||||
case errorLog
|
case errorLog
|
||||||
|
case refreshSideJITServer
|
||||||
|
case clearCache
|
||||||
case resetPairingFile
|
case resetPairingFile
|
||||||
case resetAdiPb
|
case anisetteServers
|
||||||
case advancedSettings
|
case advancedSettings
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +78,9 @@ final class SettingsViewController: UITableViewController
|
|||||||
@IBOutlet private var accountTypeLabel: UILabel!
|
@IBOutlet private var accountTypeLabel: UILabel!
|
||||||
|
|
||||||
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
|
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
|
||||||
|
@IBOutlet private var noIdleTimeoutSwitch: UISwitch!
|
||||||
|
|
||||||
|
@IBOutlet private var refreshSideJITServer: UILabel!
|
||||||
|
|
||||||
@IBOutlet private var versionLabel: UILabel!
|
@IBOutlet private var versionLabel: UILabel!
|
||||||
|
|
||||||
@@ -85,6 +93,7 @@ final class SettingsViewController: UITableViewController
|
|||||||
super.init(coder: aDecoder)
|
super.init(coder: aDecoder)
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openErrorLog(_:)), name: ToastView.openErrorLogNotification, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad()
|
override func viewDidLoad()
|
||||||
@@ -102,16 +111,36 @@ final class SettingsViewController: UITableViewController
|
|||||||
debugModeGestureRecognizer.numberOfTouchesRequired = 3
|
debugModeGestureRecognizer.numberOfTouchesRequired = 3
|
||||||
self.tableView.addGestureRecognizer(debugModeGestureRecognizer)
|
self.tableView.addGestureRecognizer(debugModeGestureRecognizer)
|
||||||
|
|
||||||
|
var versionString: String = ""
|
||||||
if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
|
if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
|
||||||
{
|
{
|
||||||
self.versionLabel.text = NSLocalizedString(String(format: "SideStore %@", version), comment: "SideStore Version")
|
versionString += "SideStore \(version)"
|
||||||
|
if let xcode = Bundle.main.object(forInfoDictionaryKey: "DTXcode") as? String {
|
||||||
|
versionString += " - Xcode \(xcode) - "
|
||||||
|
if let build = Bundle.main.object(forInfoDictionaryKey: "DTXcodeBuild") as? String {
|
||||||
|
versionString += "\(build)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let pairing = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String {
|
||||||
|
let pair_test = pairing == "<insert pairing file here>"
|
||||||
|
if !pair_test {
|
||||||
|
versionString += " - \(!pair_test)"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
self.versionLabel.text = NSLocalizedString("SideStore", comment: "")
|
versionString += "SideStore\t"
|
||||||
}
|
}
|
||||||
|
versionString += "\n\(Bundle.Info.appbundleIdentifier)"
|
||||||
|
|
||||||
|
self.versionLabel.text = NSLocalizedString(versionString, comment: "SideStore Version")
|
||||||
|
|
||||||
self.tableView.contentInset.bottom = 20
|
self.versionLabel.numberOfLines = 0
|
||||||
|
self.versionLabel.lineBreakMode = .byWordWrapping
|
||||||
|
self.versionLabel.setNeedsUpdateConstraints()
|
||||||
|
|
||||||
|
self.tableView.contentInset.bottom = 40
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
@@ -128,6 +157,18 @@ final class SettingsViewController: UITableViewController
|
|||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
|
if segue.identifier == "anisetteServers" {
|
||||||
|
let controller = UIHostingController(rootView: AnisetteServers(selected: UserDefaults.standard.menuAnisetteURL, errorCallback: {
|
||||||
|
ToastView(text: "Cleared adi.pb!", detailText: "You will need to log back into Apple ID in SideStore.").show(in: self)
|
||||||
|
}))
|
||||||
|
self.show(controller, sender: nil)
|
||||||
|
} else {
|
||||||
|
super.prepare(for: segue, sender: sender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension SettingsViewController
|
private extension SettingsViewController
|
||||||
@@ -148,6 +189,7 @@ private extension SettingsViewController
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
|
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
|
||||||
|
self.noIdleTimeoutSwitch.isOn = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||||
|
|
||||||
if self.isViewLoaded
|
if self.isViewLoaded
|
||||||
{
|
{
|
||||||
@@ -199,7 +241,7 @@ private extension SettingsViewController
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Enable Background Refresh to automatically refresh apps in the background when connected to Wi-Fi.", comment: "")
|
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Enable Background Refresh to automatically refresh apps in the background when connected to Wi-Fi. \n\nDisable the Idle Timeout toggle to allow SideStore to not let your device go to sleep during a refresh or install of any apps.", comment: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
case .instructions:
|
case .instructions:
|
||||||
@@ -280,6 +322,11 @@ private extension SettingsViewController
|
|||||||
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
|
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBAction func toggleNoIdleTimeoutEnabled(_ sender: UISwitch)
|
||||||
|
{
|
||||||
|
UserDefaults.standard.isIdleTimeoutDisableEnabled = sender.isOn
|
||||||
|
}
|
||||||
|
|
||||||
@available(iOS 14, *)
|
@available(iOS 14, *)
|
||||||
@IBAction func addRefreshAppsShortcut()
|
@IBAction func addRefreshAppsShortcut()
|
||||||
{
|
{
|
||||||
@@ -291,6 +338,39 @@ private extension SettingsViewController
|
|||||||
self.present(viewController, animated: true, completion: nil)
|
self.present(viewController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clearCache()
|
||||||
|
{
|
||||||
|
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to clear SideStore's cache?", comment: ""),
|
||||||
|
message: NSLocalizedString("This will remove all temporary files as well as backups for uninstalled apps.", comment: ""),
|
||||||
|
preferredStyle: .actionSheet)
|
||||||
|
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style) { [weak self] _ in
|
||||||
|
self?.tableView.indexPathForSelectedRow.map { self?.tableView.deselectRow(at: $0, animated: true) }
|
||||||
|
})
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Clear Cache", comment: ""), style: .destructive) { [weak self] _ in
|
||||||
|
AppManager.shared.clearAppCache { result in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self?.tableView.indexPathForSelectedRow.map { self?.tableView.deselectRow(at: $0, animated: true) }
|
||||||
|
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .success: break
|
||||||
|
case .failure(let error):
|
||||||
|
let alertController = UIAlertController(title: NSLocalizedString("Unable to Clear Cache", comment: ""), message: error.localizedDescription, preferredStyle: .alert)
|
||||||
|
alertController.addAction(.ok)
|
||||||
|
self?.present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if let popoverController = alertController.popoverPresentationController {
|
||||||
|
popoverController.sourceView = self.view
|
||||||
|
popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.present(alertController, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
@IBAction func handleDebugModeGesture(_ gestureRecognizer: UISwipeGestureRecognizer)
|
@IBAction func handleDebugModeGesture(_ gestureRecognizer: UISwipeGestureRecognizer)
|
||||||
{
|
{
|
||||||
self.debugGestureCounter += 1
|
self.debugGestureCounter += 1
|
||||||
@@ -345,6 +425,15 @@ private extension SettingsViewController
|
|||||||
self.performSegue(withIdentifier: "showPatreon", sender: nil)
|
self.performSegue(withIdentifier: "showPatreon", sender: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func openErrorLog(_: Notification) {
|
||||||
|
guard self.presentedViewController == nil else { return }
|
||||||
|
|
||||||
|
self.navigationController?.popViewController(animated: false)
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
|
self.performSegue(withIdentifier: "showErrorLog", sender: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SettingsViewController
|
extension SettingsViewController
|
||||||
@@ -386,6 +475,17 @@ extension SettingsViewController
|
|||||||
cell.style = .single
|
cell.style = .single
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if AppRefreshRow.AllCases().count == 1
|
||||||
|
{
|
||||||
|
if let cell = cell as? InsetGroupTableViewCell,
|
||||||
|
indexPath.section == Section.appRefresh.rawValue,
|
||||||
|
indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||||
|
{
|
||||||
|
cell.style = .single
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,7 +541,7 @@ extension SettingsViewController
|
|||||||
switch section
|
switch section
|
||||||
{
|
{
|
||||||
case .signIn where self.activeTeam != nil: return 1.0
|
case .signIn where self.activeTeam != nil: return 1.0
|
||||||
case .account where self.activeTeam == nil: return 1.0
|
case .account where self.activeTeam == nil: return 1.0
|
||||||
case .signIn, .patreon, .appRefresh:
|
case .signIn, .patreon, .appRefresh:
|
||||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
|
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
|
||||||
return height
|
return height
|
||||||
@@ -465,11 +565,13 @@ extension SettingsViewController
|
|||||||
switch row
|
switch row
|
||||||
{
|
{
|
||||||
case .backgroundRefresh: break
|
case .backgroundRefresh: break
|
||||||
|
case .noIdleTimeout: break
|
||||||
case .addToSiri:
|
case .addToSiri:
|
||||||
guard #available(iOS 14, *) else { return }
|
guard #available(iOS 14, *) else { return }
|
||||||
self.addRefreshAppsShortcut()
|
self.addRefreshAppsShortcut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
case .credits:
|
case .credits:
|
||||||
let row = CreditsRow.allCases[indexPath.row]
|
let row = CreditsRow.allCases[indexPath.row]
|
||||||
switch row
|
switch row
|
||||||
@@ -485,31 +587,158 @@ extension SettingsViewController
|
|||||||
switch row
|
switch row
|
||||||
{
|
{
|
||||||
case .sendFeedback:
|
case .sendFeedback:
|
||||||
if MFMailComposeViewController.canSendMail()
|
let alertController = UIAlertController(title: "Send Feedback", message: "Choose a method to send feedback:", preferredStyle: .actionSheet)
|
||||||
{
|
|
||||||
let mailViewController = MFMailComposeViewController()
|
// Option 1: GitHub
|
||||||
mailViewController.mailComposeDelegate = self
|
alertController.addAction(UIAlertAction(title: "GitHub", style: .default) { _ in
|
||||||
mailViewController.setToRecipients(["support@sidestore.io"])
|
if let githubURL = URL(string: "https://github.com/SideStore/SideStore/issues") {
|
||||||
|
let safariViewController = SFSafariViewController(url: githubURL)
|
||||||
if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
|
safariViewController.preferredControlTintColor = .altPrimary
|
||||||
{
|
self.present(safariViewController, animated: true, completion: nil)
|
||||||
mailViewController.setSubject("SideStore Beta \(version) Feedback")
|
|
||||||
}
|
}
|
||||||
else
|
})
|
||||||
{
|
|
||||||
mailViewController.setSubject("SideStore Beta Feedback")
|
// Option 2: Discord
|
||||||
|
alertController.addAction(UIAlertAction(title: "Discord", style: .default) { _ in
|
||||||
|
if let discordURL = URL(string: "https://discord.gg/sidestore-949183273383395328") {
|
||||||
|
let safariViewController = SFSafariViewController(url: discordURL)
|
||||||
|
safariViewController.preferredControlTintColor = .altPrimary
|
||||||
|
self.present(safariViewController, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Option 3: Mail
|
||||||
|
// alertController.addAction(UIAlertAction(title: "Send Email", style: .default) { _ in
|
||||||
|
// if MFMailComposeViewController.canSendMail() {
|
||||||
|
// let mailViewController = MFMailComposeViewController()
|
||||||
|
// mailViewController.mailComposeDelegate = self
|
||||||
|
// mailViewController.setToRecipients(["support@sidestore.io"])
|
||||||
|
//
|
||||||
|
// if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
|
||||||
|
// mailViewController.setSubject("SideStore Beta \(version) Feedback")
|
||||||
|
// } else {
|
||||||
|
// mailViewController.setSubject("SideStore Beta Feedback")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// self.present(mailViewController, animated: true, completion: nil)
|
||||||
|
// } else {
|
||||||
|
// let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
|
||||||
|
// toastView.show(in: self)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// Cancel action
|
||||||
|
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
|
||||||
|
|
||||||
|
// For iPad: Set the source view if presenting on iPad to avoid crashes
|
||||||
|
if let popoverController = alertController.popoverPresentationController {
|
||||||
|
popoverController.sourceView = self.view
|
||||||
|
popoverController.sourceRect = self.view.bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present the action sheet
|
||||||
|
self.present(alertController, animated: true, completion: nil)
|
||||||
|
|
||||||
|
case .refreshSideJITServer:
|
||||||
|
if #available(iOS 17, *) {
|
||||||
|
|
||||||
|
let alertController = UIAlertController(
|
||||||
|
title: NSLocalizedString("SideJITServer", comment: ""),
|
||||||
|
message: NSLocalizedString("Settings for SideJITServer", comment: ""),
|
||||||
|
preferredStyle: UIAlertController.Style.actionSheet)
|
||||||
|
|
||||||
|
|
||||||
|
if UserDefaults.standard.sidejitenable {
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Disable", comment: ""), style: .default){ _ in
|
||||||
|
UserDefaults.standard.sidejitenable = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Enable", comment: ""), style: .default){ _ in
|
||||||
|
UserDefaults.standard.sidejitenable = true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
self.present(mailViewController, animated: true, completion: nil)
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Server Address", comment: ""), style: .default){ _ in
|
||||||
}
|
let alertController1 = UIAlertController(title: "SideJITServer Address", message: "Please Enter the SideJITServer Address Below. (this is not needed if SideJITServer has already been detected)", preferredStyle: .alert)
|
||||||
else
|
|
||||||
{
|
|
||||||
let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
|
alertController1.addTextField { textField in
|
||||||
toastView.show(in: self)
|
textField.placeholder = "SideJITServer Address"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
||||||
|
alertController1.addAction(cancelAction)
|
||||||
|
|
||||||
|
|
||||||
|
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
|
||||||
|
if let text = alertController1.textFields?.first?.text {
|
||||||
|
UserDefaults.standard.textInputSideJITServerurl = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alertController1.addAction(okAction)
|
||||||
|
|
||||||
|
// Present the alert controller
|
||||||
|
self.present(alertController1, animated: true)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Refresh", comment: ""), style: .destructive){ _ in
|
||||||
|
if UserDefaults.standard.sidejitenable {
|
||||||
|
var SJSURL = ""
|
||||||
|
if (UserDefaults.standard.textInputSideJITServerurl ?? "").isEmpty {
|
||||||
|
SJSURL = "http://sidejitserver._http._tcp.local:8080"
|
||||||
|
} else {
|
||||||
|
SJSURL = UserDefaults.standard.textInputSideJITServerurl ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let url = URL(string: SJSURL + "/re/")!
|
||||||
|
|
||||||
|
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
|
||||||
|
if let error = error {
|
||||||
|
print("Error: \(error)")
|
||||||
|
} else {
|
||||||
|
// Do nothing with data or response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
||||||
|
alertController.addAction(cancelAction)
|
||||||
|
//Fix crash on iPad
|
||||||
|
alertController.popoverPresentationController?.sourceView = self.tableView
|
||||||
|
alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath)
|
||||||
|
self.present(alertController, animated: true)
|
||||||
|
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
} else {
|
||||||
|
let alertController = UIAlertController(
|
||||||
|
title: NSLocalizedString("You are not on iOS 17+ This will not work", comment: ""),
|
||||||
|
message: NSLocalizedString("This is meant for 'SideJITServer' and it only works on iOS 17+ ", comment: ""),
|
||||||
|
preferredStyle: UIAlertController.Style.actionSheet)
|
||||||
|
|
||||||
|
alertController.addAction(.cancel)
|
||||||
|
//Fix crash on iPad
|
||||||
|
alertController.popoverPresentationController?.sourceView = self.tableView
|
||||||
|
alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath)
|
||||||
|
self.present(alertController, animated: true)
|
||||||
|
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case .clearCache: self.clearCache()
|
||||||
|
|
||||||
case .resetPairingFile:
|
case .resetPairingFile:
|
||||||
|
|
||||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||||
|
|
||||||
let fm = FileManager.default
|
let fm = FileManager.default
|
||||||
|
|
||||||
let documentsPath = fm.documentsDirectory.appendingPathComponent("/\(filename)")
|
let documentsPath = fm.documentsDirectory.appendingPathComponent("/\(filename)")
|
||||||
let alertController = UIAlertController(
|
let alertController = UIAlertController(
|
||||||
title: NSLocalizedString("Are you sure to reset the pairing file?", comment: ""),
|
title: NSLocalizedString("Are you sure to reset the pairing file?", comment: ""),
|
||||||
@@ -518,11 +747,12 @@ extension SettingsViewController
|
|||||||
|
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Delete and Reset", comment: ""), style: .destructive){ _ in
|
alertController.addAction(UIAlertAction(title: NSLocalizedString("Delete and Reset", comment: ""), style: .destructive){ _ in
|
||||||
if fm.fileExists(atPath: documentsPath.path), let contents = try? String(contentsOf: documentsPath), !contents.isEmpty {
|
if fm.fileExists(atPath: documentsPath.path), let contents = try? String(contentsOf: documentsPath), !contents.isEmpty {
|
||||||
|
UserDefaults.standard.isPairingReset = true
|
||||||
try? fm.removeItem(atPath: documentsPath.path)
|
try? fm.removeItem(atPath: documentsPath.path)
|
||||||
NSLog("Pairing File Reseted")
|
NSLog("Pairing File Reseted")
|
||||||
}
|
}
|
||||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||||
let dialogMessage = UIAlertController(title: NSLocalizedString("Pairing File Reseted", comment: ""), message: NSLocalizedString("Please restart SideStore", comment: ""), preferredStyle: .alert)
|
let dialogMessage = UIAlertController(title: NSLocalizedString("Pairing File Reset", comment: ""), message: NSLocalizedString("Please restart SideStore", comment: ""), preferredStyle: .alert)
|
||||||
self.present(dialogMessage, animated: true, completion: nil)
|
self.present(dialogMessage, animated: true, completion: nil)
|
||||||
})
|
})
|
||||||
alertController.addAction(.cancel)
|
alertController.addAction(.cancel)
|
||||||
@@ -531,25 +761,12 @@ extension SettingsViewController
|
|||||||
alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath)
|
alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath)
|
||||||
self.present(alertController, animated: true)
|
self.present(alertController, animated: true)
|
||||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||||
case .resetAdiPb:
|
|
||||||
let alertController = UIAlertController(
|
|
||||||
title: NSLocalizedString("Are you sure you want to reset the adi.pb file?", comment: ""),
|
|
||||||
message: NSLocalizedString("The adi.pb file is used to generate anisette data, which is required to log into an Apple ID. If you are having issues with account related things, you can try this. However, you will be required to do 2FA again. This will do nothing if you are using an older anisette server.", comment: ""),
|
|
||||||
preferredStyle: UIAlertController.Style.actionSheet)
|
|
||||||
|
|
||||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Reset adi.pb", comment: ""), style: .destructive){ _ in
|
case .anisetteServers:
|
||||||
if Keychain.shared.adiPb != nil {
|
self.prepare(for: UIStoryboardSegue(identifier: "anisetteServers", source: self, destination: UIHostingController(rootView: AnisetteServers(selected: "", errorCallback: {
|
||||||
Keychain.shared.adiPb = nil
|
ToastView(text: "Reset adi.pb", detailText: "Buh").show(in: self)
|
||||||
print("Cleared adi.pb from keychain")
|
}))), sender: nil)
|
||||||
}
|
// self.performSegue(withIdentifier: "anisetteServers", sender: nil)
|
||||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
|
||||||
})
|
|
||||||
alertController.addAction(.cancel)
|
|
||||||
//Fix crash on iPad
|
|
||||||
alertController.popoverPresentationController?.sourceView = self.tableView
|
|
||||||
alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath)
|
|
||||||
self.present(alertController, animated: true)
|
|
||||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
|
||||||
case .advancedSettings:
|
case .advancedSettings:
|
||||||
// Create the URL that deep links to your app's custom settings.
|
// Create the URL that deep links to your app's custom settings.
|
||||||
if let url = URL(string: UIApplication.openSettingsURLString) {
|
if let url = URL(string: UIApplication.openSettingsURLString) {
|
||||||
@@ -559,6 +776,7 @@ extension SettingsViewController
|
|||||||
ELOG("UIApplication.openSettingsURLString invalid")
|
ELOG("UIApplication.openSettingsURLString invalid")
|
||||||
}
|
}
|
||||||
case .refreshAttempts, .errorLog: break
|
case .refreshAttempts, .errorLog: break
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default: break
|
default: break
|
||||||
|
|||||||
@@ -12,17 +12,22 @@ import CoreData
|
|||||||
import AltStoreCore
|
import AltStoreCore
|
||||||
import Roxas
|
import Roxas
|
||||||
|
|
||||||
struct SourceError: LocalizedError
|
struct SourceError: ALTLocalizedError
|
||||||
{
|
{
|
||||||
enum Code
|
enum Code: Int, ALTErrorCode
|
||||||
{
|
{
|
||||||
|
typealias Error = SourceError
|
||||||
|
|
||||||
case unsupported
|
case unsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
var code: Code
|
var code: Code
|
||||||
|
var errorTitle: String?
|
||||||
|
var errorFailure: String?
|
||||||
|
|
||||||
@Managed var source: Source
|
@Managed var source: Source
|
||||||
|
|
||||||
var errorDescription: String? {
|
var errorFailureReason: String {
|
||||||
switch self.code
|
switch self.code
|
||||||
{
|
{
|
||||||
case .unsupported: return String(format: NSLocalizedString("The source “%@” is not supported by this version of SideStore.", comment: ""), self.$source.name)
|
case .unsupported: return String(format: NSLocalizedString("The source “%@” is not supported by this version of SideStore.", comment: ""), self.$source.name)
|
||||||
@@ -197,7 +202,7 @@ private extension SourcesViewController
|
|||||||
{
|
{
|
||||||
let alertController = UIAlertController(title: NSLocalizedString("Add Source", comment: ""), message: nil, preferredStyle: .alert)
|
let alertController = UIAlertController(title: NSLocalizedString("Add Source", comment: ""), message: nil, preferredStyle: .alert)
|
||||||
alertController.addTextField { (textField) in
|
alertController.addTextField { (textField) in
|
||||||
textField.placeholder = "https://apps.altstore.io"
|
textField.placeholder = "https://apps.sidestore.io"
|
||||||
textField.textContentType = .URL
|
textField.textContentType = .URL
|
||||||
}
|
}
|
||||||
alertController.addAction(.cancel)
|
alertController.addAction(.cancel)
|
||||||
@@ -545,19 +550,19 @@ extension SourcesViewController: UICollectionViewDelegateFlowLayout
|
|||||||
footerView.textView.delegate = self
|
footerView.textView.delegate = self
|
||||||
|
|
||||||
let attributedText = NSMutableAttributedString(
|
let attributedText = NSMutableAttributedString(
|
||||||
string: NSLocalizedString("SideStore has reviewed these sources to make sure they meet our safety standards.\n\nSupport for untrusted sources is currently in beta, but you can help test them out by", comment: ""),
|
string: NSLocalizedString("SideStore has reviewed these sources to make sure they meet our safety standards.", comment: ""),
|
||||||
attributes: [.font: font, .foregroundColor: UIColor.gray]
|
attributes: [.font: font, .foregroundColor: UIColor.gray]
|
||||||
)
|
)
|
||||||
attributedText.mutableString.append(" ")
|
//attributedText.mutableString.append(" ")
|
||||||
|
|
||||||
let boldedFont = UIFont(descriptor: font.fontDescriptor.withSymbolicTraits(.traitBold)!, size: font.pointSize)
|
//let boldedFont = UIFont(descriptor: font.fontDescriptor.withSymbolicTraits(.traitBold)!, size: font.pointSize)
|
||||||
let openPatreonURL = URL(string: "https://SideStore.io/patreon")!
|
//let openPatreonURL = URL(string: "https://SideStore.io/")!
|
||||||
|
|
||||||
let joinPatreonText = NSAttributedString(
|
// let joinPatreonText = NSAttributedString(
|
||||||
string: NSLocalizedString("joining our Patreon.", comment: ""),
|
// string: NSLocalizedString("", comment: ""),
|
||||||
attributes: [.font: boldedFont, .link: openPatreonURL, .underlineColor: UIColor.clear]
|
// attributes: [.font: boldedFont, .link: openPatreonURL, .underlineColor: UIColor.clear]
|
||||||
)
|
//)
|
||||||
attributedText.append(joinPatreonText)
|
//attributedText.append(joinPatreonText)
|
||||||
|
|
||||||
footerView.textView.attributedText = attributedText
|
footerView.textView.attributedText = attributedText
|
||||||
footerView.textView.textAlignment = .natural
|
footerView.textView.textAlignment = .natural
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ final class TabBarController: UITabBarController
|
|||||||
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.importApp(_:)), name: AppDelegate.importAppDeepLinkNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.importApp(_:)), name: AppDelegate.importAppDeepLinkNotification, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.presentSources(_:)), name: AppDelegate.addSourceDeepLinkNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.presentSources(_:)), name: AppDelegate.addSourceDeepLinkNotification, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openErrorLog(_:)), name: ToastView.openErrorLogNotification, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool)
|
override func viewDidAppear(_ animated: Bool)
|
||||||
@@ -141,4 +142,7 @@ private extension TabBarController
|
|||||||
{
|
{
|
||||||
self.selectedIndex = Tab.myApps.rawValue
|
self.selectedIndex = Tab.myApps.rawValue
|
||||||
}
|
}
|
||||||
|
@objc func openErrorLog(_: Notification){
|
||||||
|
self.selectedIndex = Tab.settings.rawValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,23 +10,27 @@ import Foundation
|
|||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
@propertyWrapper @dynamicMemberLookup
|
@propertyWrapper @dynamicMemberLookup
|
||||||
struct Managed<ManagedObject: NSManagedObject>
|
struct Managed<ManagedObject>
|
||||||
{
|
{
|
||||||
var wrappedValue: ManagedObject {
|
var wrappedValue: ManagedObject {
|
||||||
didSet {
|
didSet {
|
||||||
self.managedObjectContext = self.wrappedValue.managedObjectContext
|
self.managedObjectContext = self.managedObject?.managedObjectContext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private var managedObjectContext: NSManagedObjectContext?
|
|
||||||
|
|
||||||
var projectedValue: Managed<ManagedObject> {
|
var projectedValue: Managed<ManagedObject> {
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var managedObjectContext: NSManagedObjectContext?
|
||||||
|
private var managedObject: NSManagedObject? {
|
||||||
|
return self.wrappedValue as? NSManagedObject
|
||||||
|
}
|
||||||
|
|
||||||
init(wrappedValue: ManagedObject)
|
init(wrappedValue: ManagedObject)
|
||||||
{
|
{
|
||||||
self.wrappedValue = wrappedValue
|
self.wrappedValue = wrappedValue
|
||||||
self.managedObjectContext = wrappedValue.managedObjectContext
|
self.managedObjectContext = self.managedObject?.managedObjectContext
|
||||||
}
|
}
|
||||||
|
|
||||||
subscript<T>(dynamicMember keyPath: KeyPath<ManagedObject, T>) -> T
|
subscript<T>(dynamicMember keyPath: KeyPath<ManagedObject, T>) -> T
|
||||||
@@ -46,4 +50,18 @@ struct Managed<ManagedObject: NSManagedObject>
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optionals
|
||||||
|
subscript<Wrapped, T>(dynamicMember keyPath: KeyPath<Wrapped, T>) -> T? where ManagedObject == Optional<Wrapped> {
|
||||||
|
var result: T?
|
||||||
|
|
||||||
|
if let context = self.managedObjectContext {
|
||||||
|
context.performAndWait {
|
||||||
|
result = self.wrappedValue?[keyPath: keyPath] as? T
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = self.wrappedValue?[keyPath: keyPath] as? T
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,5 +23,6 @@ FOUNDATION_EXPORT const unsigned char AltStoreCoreVersionString[];
|
|||||||
// Shared
|
// Shared
|
||||||
#import <AltStoreCore/ALTConstants.h>
|
#import <AltStoreCore/ALTConstants.h>
|
||||||
#import <AltStoreCore/ALTConnection.h>
|
#import <AltStoreCore/ALTConnection.h>
|
||||||
|
#import <AltStoreCore/ALTWrappedError.h>
|
||||||
#import <AltStoreCore/NSError+ALTServerError.h>
|
#import <AltStoreCore/NSError+ALTServerError.h>
|
||||||
#import <AltStoreCore/CFNotificationName+AltStore.h>
|
#import <AltStoreCore/CFNotificationName+AltStore.h>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// OperatingSystemVersion+Comparable.swift
|
||||||
|
// AltStoreCore
|
||||||
|
//
|
||||||
|
// Created by nythepegasus on 5/9/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension OperatingSystemVersion: Comparable {
|
||||||
|
public static func ==(lhs: OperatingSystemVersion, rhs: OperatingSystemVersion) -> Bool {
|
||||||
|
return lhs.majorVersion == rhs.majorVersion && lhs.minorVersion == rhs.minorVersion && lhs.patchVersion == rhs.patchVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func <(lhs: OperatingSystemVersion, rhs: OperatingSystemVersion) -> Bool {
|
||||||
|
return lhs.stringValue.compare(rhs.stringValue, options: .numeric) == .orderedAscending
|
||||||
|
}
|
||||||
|
}
|
||||||
14
AltStoreCore/Extensions/String+SideStore.swift
Normal file
14
AltStoreCore/Extensions/String+SideStore.swift
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//
|
||||||
|
// String+SideStore.swift
|
||||||
|
// AltStoreCore
|
||||||
|
//
|
||||||
|
// Created by nythepegasus on 5/9/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public extension String {
|
||||||
|
init(formatted: String, comment: String? = nil, _ args: String...) {
|
||||||
|
self.init(format: NSLocalizedString(formatted, comment: comment ?? ""), args)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,11 +22,17 @@ public extension UserDefaults
|
|||||||
@NSManaged var firstLaunch: Date?
|
@NSManaged var firstLaunch: Date?
|
||||||
@NSManaged var requiresAppGroupMigration: Bool
|
@NSManaged var requiresAppGroupMigration: Bool
|
||||||
@NSManaged var textServer: Bool
|
@NSManaged var textServer: Bool
|
||||||
|
@NSManaged var sidejitenable: Bool
|
||||||
|
@NSManaged var textInputSideJITServerurl: String?
|
||||||
@NSManaged var textInputAnisetteURL: String?
|
@NSManaged var textInputAnisetteURL: String?
|
||||||
@NSManaged var customAnisetteURL: String?
|
@NSManaged var customAnisetteURL: String?
|
||||||
|
@NSManaged var menuAnisetteURL: String
|
||||||
|
@NSManaged var menuAnisetteList: String
|
||||||
@NSManaged var preferredServerID: String?
|
@NSManaged var preferredServerID: String?
|
||||||
|
|
||||||
@NSManaged var isBackgroundRefreshEnabled: Bool
|
@NSManaged var isBackgroundRefreshEnabled: Bool
|
||||||
|
@NSManaged var isIdleTimeoutDisableEnabled: Bool
|
||||||
|
@NSManaged var isPairingReset: Bool
|
||||||
@NSManaged var isDebugModeEnabled: Bool
|
@NSManaged var isDebugModeEnabled: Bool
|
||||||
@NSManaged var presentedLaunchReminderNotification: Bool
|
@NSManaged var presentedLaunchReminderNotification: Bool
|
||||||
|
|
||||||
@@ -72,11 +78,14 @@ public extension UserDefaults
|
|||||||
|
|
||||||
let defaults = [
|
let defaults = [
|
||||||
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
||||||
|
#keyPath(UserDefaults.isIdleTimeoutDisableEnabled): true,
|
||||||
|
#keyPath(UserDefaults.isPairingReset): true,
|
||||||
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
||||||
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
|
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
|
||||||
#keyPath(UserDefaults.localServerSupportsRefreshing): localServerSupportsRefreshing,
|
#keyPath(UserDefaults.localServerSupportsRefreshing): localServerSupportsRefreshing,
|
||||||
#keyPath(UserDefaults.requiresAppGroupMigration): true
|
#keyPath(UserDefaults.requiresAppGroupMigration): true,
|
||||||
]
|
#keyPath(UserDefaults.menuAnisetteURL): "https://ani.sidestore.io"
|
||||||
|
] as [String : Any]
|
||||||
|
|
||||||
UserDefaults.standard.register(defaults: defaults)
|
UserDefaults.standard.register(defaults: defaults)
|
||||||
UserDefaults.shared.register(defaults: defaults)
|
UserDefaults.shared.register(defaults: defaults)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ public class AppVersion: NSManagedObject, Decodable, Fetchable
|
|||||||
|
|
||||||
/* Relationships */
|
/* Relationships */
|
||||||
@NSManaged public private(set) var app: StoreApp?
|
@NSManaged public private(set) var app: StoreApp?
|
||||||
@NSManaged public private(set) var latestVersionApp: StoreApp?
|
@NSManaged @objc(latestVersionApp) public internal(set) var latestSupportedVersionApp: StoreApp?
|
||||||
|
|
||||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
{
|
{
|
||||||
@@ -54,6 +54,8 @@ public class AppVersion: NSManagedObject, Decodable, Fetchable
|
|||||||
case localizedDescription
|
case localizedDescription
|
||||||
case downloadURL
|
case downloadURL
|
||||||
case size
|
case size
|
||||||
|
case minOSVersion
|
||||||
|
case maxOSVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
public required init(from decoder: Decoder) throws
|
public required init(from decoder: Decoder) throws
|
||||||
@@ -72,6 +74,9 @@ public class AppVersion: NSManagedObject, Decodable, Fetchable
|
|||||||
|
|
||||||
self.downloadURL = try container.decode(URL.self, forKey: .downloadURL)
|
self.downloadURL = try container.decode(URL.self, forKey: .downloadURL)
|
||||||
self.size = try container.decode(Int64.self, forKey: .size)
|
self.size = try container.decode(Int64.self, forKey: .size)
|
||||||
|
|
||||||
|
self._minOSVersion = try container.decodeIfPresent(String.self, forKey: .minOSVersion)
|
||||||
|
self._maxOSVersion = try container.decodeIfPresent(String.self, forKey: .maxOSVersion)
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -113,4 +118,13 @@ public extension AppVersion
|
|||||||
|
|
||||||
return appVersion
|
return appVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isSupported: Bool {
|
||||||
|
if let minOSVersion = self.minOSVersion, !ProcessInfo.processInfo.isOperatingSystemAtLeast(minOSVersion) {
|
||||||
|
return false
|
||||||
|
} else if let maxOSVersion = self.maxOSVersion, ProcessInfo.processInfo.operatingSystemVersion > maxOSVersion {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ import Roxas
|
|||||||
|
|
||||||
extension CFNotificationName
|
extension CFNotificationName
|
||||||
{
|
{
|
||||||
fileprivate static let willAccessDatabase = CFNotificationName("com.rileytestut.AltStore.WillAccessDatabase" as CFString)
|
fileprivate static let willMigrateDatabase = CFNotificationName("com.rileytestut.AltStore.WillMigrateDatabase" as CFString)
|
||||||
}
|
}
|
||||||
|
|
||||||
private let ReceivedWillAccessDatabaseNotification: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void = { (center, observer, name, object, userInfo) in
|
private let ReceivedWillMigrateDatabaseNotification: @convention(c) (CFNotificationCenter?, UnsafeMutableRawPointer?, CFNotificationName?, UnsafeRawPointer?, CFDictionary?) -> Void = { (center, observer, name, object, userInfo) in
|
||||||
DatabaseManager.shared.receivedWillAccessDatabaseNotification()
|
DatabaseManager.shared.receivedWillMigrateDatabaseNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate class PersistentContainer: RSTPersistentContainer
|
fileprivate class PersistentContainer: RSTPersistentContainer
|
||||||
@@ -52,15 +52,15 @@ public class DatabaseManager
|
|||||||
private let coordinator = NSFileCoordinator()
|
private let coordinator = NSFileCoordinator()
|
||||||
private let coordinatorQueue = OperationQueue()
|
private let coordinatorQueue = OperationQueue()
|
||||||
|
|
||||||
private var ignoreWillAccessDatabaseNotification = false
|
private var ignoreWillMigrateDatabaseNotification = false
|
||||||
|
|
||||||
private init()
|
private init()
|
||||||
{
|
{
|
||||||
self.persistentContainer = PersistentContainer(name: "AltStore", bundle: Bundle(for: DatabaseManager.self))
|
self.persistentContainer = PersistentContainer(name: "AltStore", bundle: Bundle(for: DatabaseManager.self))
|
||||||
self.persistentContainer.preferredMergePolicy = MergePolicy()
|
self.persistentContainer.preferredMergePolicy = MergePolicy()
|
||||||
|
|
||||||
let observer = Unmanaged.passUnretained(self).toOpaque()
|
let observer = Unmanaged.passUnretained(self).toOpaque()
|
||||||
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), observer, ReceivedWillAccessDatabaseNotification, CFNotificationName.willAccessDatabase.rawValue, nil, .deliverImmediately)
|
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), observer, ReceivedWillMigrateDatabaseNotification, CFNotificationName.willMigrateDatabase.rawValue, nil, .deliverImmediately)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,10 +87,13 @@ public extension DatabaseManager
|
|||||||
|
|
||||||
guard !self.isStarted else { return finish(nil) }
|
guard !self.isStarted else { return finish(nil) }
|
||||||
|
|
||||||
// Quit any other running AltStore processes to prevent concurrent database access during and after migration.
|
if self.persistentContainer.isMigrationRequired {
|
||||||
self.ignoreWillAccessDatabaseNotification = true
|
|
||||||
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), .willAccessDatabase, nil, nil, true)
|
// Quit any other running AltStore processes to prevent concurrent database access during and after migration.
|
||||||
|
self.ignoreWillMigrateDatabaseNotification = true
|
||||||
|
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), .willMigrateDatabase, nil, nil, true)
|
||||||
|
}
|
||||||
|
|
||||||
self.migrateDatabaseToAppGroupIfNeeded { (result) in
|
self.migrateDatabaseToAppGroupIfNeeded { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
@@ -229,7 +232,7 @@ private extension DatabaseManager
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
storeApp = StoreApp.makeAltStoreApp(in: context)
|
storeApp = StoreApp.makeAltStoreApp(in: context)
|
||||||
storeApp.latestVersion?.version = localApp.version
|
storeApp.latestSupportedVersion?.version = localApp.version
|
||||||
storeApp.source = altStoreSource
|
storeApp.source = altStoreSource
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,13 +420,13 @@ private extension DatabaseManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func receivedWillAccessDatabaseNotification()
|
func receivedWillMigrateDatabaseNotification()
|
||||||
{
|
{
|
||||||
defer { self.ignoreWillAccessDatabaseNotification = false }
|
defer { self.ignoreWillMigrateDatabaseNotification = false }
|
||||||
|
|
||||||
// Ignore notifications sent by the current process.
|
// Ignore notifications sent by the current process.
|
||||||
guard !self.ignoreWillAccessDatabaseNotification else { return }
|
guard !self.ignoreWillMigrateDatabaseNotification else { return }
|
||||||
|
|
||||||
exit(104)
|
exit(104)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,14 +62,14 @@ public class InstalledApp: NSManagedObject, InstalledAppProtocol
|
|||||||
|
|
||||||
@objc public var hasUpdate: Bool {
|
@objc public var hasUpdate: Bool {
|
||||||
if self.storeApp == nil { return false }
|
if self.storeApp == nil { return false }
|
||||||
if self.storeApp!.latestVersion == nil { return false }
|
if self.storeApp!.latestSupportedVersion == nil { return false }
|
||||||
|
|
||||||
let currentVersion = SemanticVersion(self.version)
|
let currentVersion = SemanticVersion(self.version)
|
||||||
let latestVersion = SemanticVersion(self.storeApp!.latestVersion!.version)
|
let latestVersion = SemanticVersion(self.storeApp!.latestSupportedVersion!.version)
|
||||||
|
|
||||||
if currentVersion == nil || latestVersion == nil {
|
if currentVersion == nil || latestVersion == nil {
|
||||||
// One of the versions is not valid SemVer, fall back to comparing the version strings by character
|
// One of the versions is not valid SemVer, fall back to comparing the version strings by character
|
||||||
return self.version < self.storeApp!.latestVersion!.version
|
return self.version < self.storeApp!.latestSupportedVersion!.version
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentVersion! < latestVersion!
|
return currentVersion! < latestVersion!
|
||||||
@@ -163,8 +163,8 @@ public extension InstalledApp
|
|||||||
class func updatesFetchRequest() -> NSFetchRequest<InstalledApp>
|
class func updatesFetchRequest() -> NSFetchRequest<InstalledApp>
|
||||||
{
|
{
|
||||||
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
|
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
|
||||||
fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K == YES",
|
fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K != nil AND %K != %K",
|
||||||
#keyPath(InstalledApp.isActive), #keyPath(InstalledApp.hasUpdate))
|
#keyPath(InstalledApp.isActive), #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.version), #keyPath(InstalledApp.storeApp.latestSupportedVersion.version))
|
||||||
return fetchRequest
|
return fetchRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,14 +275,12 @@ public extension InstalledApp
|
|||||||
|
|
||||||
do { try FileManager.default.createDirectory(at: appsDirectoryURL, withIntermediateDirectories: true, attributes: nil) }
|
do { try FileManager.default.createDirectory(at: appsDirectoryURL, withIntermediateDirectories: true, attributes: nil) }
|
||||||
catch { print("Creating App Directory Error: \(error)") }
|
catch { print("Creating App Directory Error: \(error)") }
|
||||||
print("`appsDirectoryURL` is set to: \(appsDirectoryURL.absoluteString)")
|
|
||||||
return appsDirectoryURL
|
return appsDirectoryURL
|
||||||
}
|
}
|
||||||
|
|
||||||
class var legacyAppsDirectoryURL: URL {
|
class var legacyAppsDirectoryURL: URL {
|
||||||
let baseDirectory = FileManager.default.applicationSupportDirectory
|
let baseDirectory = FileManager.default.applicationSupportDirectory
|
||||||
let appsDirectoryURL = baseDirectory.appendingPathComponent("Apps")
|
let appsDirectoryURL = baseDirectory.appendingPathComponent("Apps")
|
||||||
print("legacy `appsDirectoryURL` is set to: \(appsDirectoryURL.absoluteString)")
|
|
||||||
return appsDirectoryURL
|
return appsDirectoryURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ extension LoggedError
|
|||||||
case deactivate
|
case deactivate
|
||||||
case backup
|
case backup
|
||||||
case restore
|
case restore
|
||||||
|
case connection
|
||||||
|
case enableJIT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +68,12 @@ public class LoggedError: NSManagedObject, Fetchable
|
|||||||
self.date = date
|
self.date = date
|
||||||
self._operation = operation?.rawValue
|
self._operation = operation?.rawValue
|
||||||
|
|
||||||
let nsError = error as NSError
|
let nsError: NSError
|
||||||
|
if let error = error as? ALTServerError, error.code == .underlyingError, let underlyingError = error.underlyingError {
|
||||||
|
nsError = underlyingError as NSError
|
||||||
|
} else {
|
||||||
|
nsError = error as NSError
|
||||||
|
}
|
||||||
self.domain = nsError.domain
|
self.domain = nsError.domain
|
||||||
self.code = Int32(nsError.code)
|
self.code = Int32(nsError.code)
|
||||||
self.userInfo = nsError.userInfo
|
self.userInfo = nsError.userInfo
|
||||||
@@ -91,7 +98,7 @@ public extension LoggedError
|
|||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
var error: Error {
|
var error: NSError {
|
||||||
let nsError = NSError(domain: self.domain, code: Int(self.code), userInfo: self.userInfo)
|
let nsError = NSError(domain: self.domain, code: Int(self.code), userInfo: self.userInfo)
|
||||||
return nsError
|
return nsError
|
||||||
}
|
}
|
||||||
@@ -113,6 +120,8 @@ public extension LoggedError
|
|||||||
case .deactivate: return String(format: NSLocalizedString("Deactivate %@ Failed", comment: ""), self.appName)
|
case .deactivate: return String(format: NSLocalizedString("Deactivate %@ Failed", comment: ""), self.appName)
|
||||||
case .backup: return String(format: NSLocalizedString("Backup %@ Failed", comment: ""), self.appName)
|
case .backup: return String(format: NSLocalizedString("Backup %@ Failed", comment: ""), self.appName)
|
||||||
case .restore: return String(format: NSLocalizedString("Restore %@ Failed", comment: ""), self.appName)
|
case .restore: return String(format: NSLocalizedString("Restore %@ Failed", comment: ""), self.appName)
|
||||||
|
case .connection: return String(format: NSLocalizedString("Connection during %@ Failed", comment: ""), self.appName)
|
||||||
|
case .enableJIT: return String(format: NSLocalizedString("Enabling JIT for %@ Failed", comment: ""), self.appName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy
|
|||||||
let conflictingAppVersions = conflict.conflictingObjects.lazy.compactMap { $0 as? AppVersion }
|
let conflictingAppVersions = conflict.conflictingObjects.lazy.compactMap { $0 as? AppVersion }
|
||||||
|
|
||||||
// Primary AppVersion == AppVersion whose latestVersionApp.latestVersion points back to itself.
|
// Primary AppVersion == AppVersion whose latestVersionApp.latestVersion points back to itself.
|
||||||
if let primaryAppVersion = conflictingAppVersions.first(where: { $0.latestVersionApp?.latestVersion == $0 }),
|
if let primaryAppVersion = conflictingAppVersions.first(where: { $0.latestSupportedVersionApp?.latestSupportedVersion == $0 }),
|
||||||
let secondaryAppVersion = conflictingAppVersions.first(where: { $0 != primaryAppVersion })
|
let secondaryAppVersion = conflictingAppVersions.first(where: { $0 != primaryAppVersion })
|
||||||
{
|
{
|
||||||
secondaryAppVersion.managedObjectContext?.delete(secondaryAppVersion)
|
secondaryAppVersion.managedObjectContext?.delete(secondaryAppVersion)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ fileprivate extension NSManagedObject
|
|||||||
|
|
||||||
func setStoreAppLatestVersion(_ appVersion: NSManagedObject)
|
func setStoreAppLatestVersion(_ appVersion: NSManagedObject)
|
||||||
{
|
{
|
||||||
self.setValue(appVersion, forKey: #keyPath(StoreApp.latestVersion))
|
self.setValue(appVersion, forKey: #keyPath(StoreApp.latestSupportedVersion))
|
||||||
|
|
||||||
let versions = NSOrderedSet(array: [appVersion])
|
let versions = NSOrderedSet(array: [appVersion])
|
||||||
self.setValue(versions, forKey: #keyPath(StoreApp._versions))
|
self.setValue(versions, forKey: #keyPath(StoreApp._versions))
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
@NSManaged @objc(source) public var _source: Source?
|
@NSManaged @objc(source) public var _source: Source?
|
||||||
@NSManaged @objc(permissions) public var _permissions: NSOrderedSet
|
@NSManaged @objc(permissions) public var _permissions: NSOrderedSet
|
||||||
|
|
||||||
@NSManaged public private(set) var latestVersion: AppVersion?
|
@NSManaged @objc(latestVersion) public private(set) var latestSupportedVersion: AppVersion?
|
||||||
@NSManaged @objc(versions) public private(set) var _versions: NSOrderedSet
|
@NSManaged @objc(versions) public private(set) var _versions: NSOrderedSet
|
||||||
|
|
||||||
@NSManaged public private(set) var loggedErrors: NSSet /* Set<LoggedError> */ // Use NSSet to avoid eagerly fetching values.
|
@NSManaged public private(set) var loggedErrors: NSSet /* Set<LoggedError> */ // Use NSSet to avoid eagerly fetching values.
|
||||||
@@ -169,31 +169,6 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
return self._versions.array as! [AppVersion]
|
return self._versions.array as! [AppVersion]
|
||||||
}
|
}
|
||||||
|
|
||||||
@nonobjc public var size: Int64? {
|
|
||||||
guard let version = self.latestVersion else { return nil }
|
|
||||||
return version.size
|
|
||||||
}
|
|
||||||
|
|
||||||
@nonobjc public var version: String? {
|
|
||||||
guard let version = self.latestVersion else { return nil }
|
|
||||||
return version.version
|
|
||||||
}
|
|
||||||
|
|
||||||
@nonobjc public var versionDescription: String? {
|
|
||||||
guard let version = self.latestVersion else { return nil }
|
|
||||||
return version.localizedDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
@nonobjc public var versionDate: Date? {
|
|
||||||
guard let version = self.latestVersion else { return nil }
|
|
||||||
return version.date
|
|
||||||
}
|
|
||||||
|
|
||||||
@nonobjc public var downloadURL: URL? {
|
|
||||||
guard let version = self.latestVersion else { return nil }
|
|
||||||
return version.downloadURL
|
|
||||||
}
|
|
||||||
|
|
||||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
{
|
{
|
||||||
super.init(entity: entity, insertInto: context)
|
super.init(entity: entity, insertInto: context)
|
||||||
@@ -314,16 +289,30 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension StoreApp
|
internal extension StoreApp
|
||||||
{
|
{
|
||||||
func setVersions(_ versions: [AppVersion])
|
func setVersions(_ versions: [AppVersion])
|
||||||
{
|
{
|
||||||
guard let latestVersion = versions.first else { preconditionFailure("StoreApp must have at least one AppVersion.") }
|
|
||||||
|
|
||||||
self.latestVersion = latestVersion
|
|
||||||
self._versions = NSOrderedSet(array: versions)
|
self._versions = NSOrderedSet(array: versions)
|
||||||
|
|
||||||
|
let latestSupportedVersion = versions.first(where: { $0.isSupported })
|
||||||
|
self.latestSupportedVersion = latestSupportedVersion
|
||||||
|
|
||||||
|
for case let version as AppVersion in self._versions
|
||||||
|
{
|
||||||
|
if version == latestSupportedVersion
|
||||||
|
{
|
||||||
|
version.latestSupportedVersionApp = self
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ensure we replace any previous relationship when merging.
|
||||||
|
version.latestSupportedVersionApp = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Preserve backwards compatibility by assigning legacy property values.
|
// Preserve backwards compatibility by assigning legacy property values.
|
||||||
|
guard let latestVersion = versions.first else { preconditionFailure("StoreApp must have at least one AppVersion.") }
|
||||||
self._version = latestVersion.version
|
self._version = latestVersion.version
|
||||||
self._versionDate = latestVersion.date
|
self._versionDate = latestVersion.date
|
||||||
self._versionDescription = latestVersion.localizedDescription
|
self._versionDescription = latestVersion.localizedDescription
|
||||||
@@ -334,6 +323,10 @@ private extension StoreApp
|
|||||||
|
|
||||||
public extension StoreApp
|
public extension StoreApp
|
||||||
{
|
{
|
||||||
|
var latestAvailableVersion: AppVersion? {
|
||||||
|
return self._versions.firstObject as? AppVersion
|
||||||
|
}
|
||||||
|
|
||||||
@nonobjc class func fetchRequest() -> NSFetchRequest<StoreApp>
|
@nonobjc class func fetchRequest() -> NSFetchRequest<StoreApp>
|
||||||
{
|
{
|
||||||
return NSFetchRequest<StoreApp>(entityName: "StoreApp")
|
return NSFetchRequest<StoreApp>(entityName: "StoreApp")
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ extension ALTApplication: AppProtocol
|
|||||||
extension StoreApp: AppProtocol
|
extension StoreApp: AppProtocol
|
||||||
{
|
{
|
||||||
public var url: URL? {
|
public var url: URL? {
|
||||||
return self.downloadURL
|
return self.latestAvailableVersion?.downloadURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,3 +50,17 @@ extension InstalledApp: AppProtocol
|
|||||||
return self.fileURL
|
return self.fileURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension AppVersion: AppProtocol {
|
||||||
|
public var name: String {
|
||||||
|
return self.app?.name ?? self.bundleIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
public var bundleIdentifier: String {
|
||||||
|
return self.appBundleID
|
||||||
|
}
|
||||||
|
|
||||||
|
public var url: URL? {
|
||||||
|
return self.downloadURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>ALTAppGroups</key>
|
<key>ALTAppGroups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>group.com.SideStore.SideStore</string>
|
|
||||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||||
|
<string>group.com.SideStore.SideStore</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Configuration settings file format documentation can be found at:
|
// Configuration settings file format documentation can be found at:
|
||||||
// https://help.apple.com/xcode/#/dev745c5c974
|
// https://help.apple.com/xcode/#/dev745c5c974
|
||||||
|
|
||||||
MARKETING_VERSION = 0.5.2
|
MARKETING_VERSION = 0.5.8
|
||||||
CURRENT_PROJECT_VERSION = 5020
|
CURRENT_PROJECT_VERSION = 5080
|
||||||
|
|
||||||
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
||||||
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
||||||
|
|||||||
1729
Dependencies/Custom/libplist/src/plist.c
vendored
1729
Dependencies/Custom/libplist/src/plist.c
vendored
File diff suppressed because it is too large
Load Diff
2
Dependencies/Roxas
vendored
2
Dependencies/Roxas
vendored
Submodule Dependencies/Roxas updated: c28b400621...03f6013972
2
Dependencies/libplist
vendored
2
Dependencies/libplist
vendored
Submodule Dependencies/libplist updated: 2cd858c679...258d3c24aa
@@ -6,6 +6,7 @@
|
|||||||
[](https://makeapullrequest.com)
|
[](https://makeapullrequest.com)
|
||||||
[](https://github.com/SideStore/SideStore/actions/workflows/nightly.yml)
|
[](https://github.com/SideStore/SideStore/actions/workflows/nightly.yml)
|
||||||
[](https://github.com/SideStore/SideStore/actions/workflows/beta.yml)
|
[](https://github.com/SideStore/SideStore/actions/workflows/beta.yml)
|
||||||
|
[](https://discord.gg/sidestore-949183273383395328)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,16 @@
|
|||||||
|
|
||||||
#import "NSError+ALTServerError.h"
|
#import "NSError+ALTServerError.h"
|
||||||
|
|
||||||
NSErrorDomain const AltServerErrorDomain = @"com.rileytestut.AltServer";
|
#if TARGET_OS_OSX
|
||||||
NSErrorDomain const AltServerInstallationErrorDomain = @"com.rileytestut.AltServer.Installation";
|
#import "AltServer-Swift.h"
|
||||||
NSErrorDomain const AltServerConnectionErrorDomain = @"com.rileytestut.AltServer.Connection";
|
#else
|
||||||
|
#import "AltStoreCore/AltStoreCore-Swift.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
NSErrorDomain const AltServerErrorDomain = @"AltServer.ServerError";
|
||||||
|
NSErrorDomain const AltServerInstallationErrorDomain = @"AltServer.InstallationError";
|
||||||
|
NSErrorDomain const AltServerConnectionErrorDomain = @"AltServer.ConnectionError";
|
||||||
|
|
||||||
|
|
||||||
NSErrorUserInfoKey const ALTUnderlyingErrorDomainErrorKey = @"underlyingErrorDomain";
|
NSErrorUserInfoKey const ALTUnderlyingErrorDomainErrorKey = @"underlyingErrorDomain";
|
||||||
NSErrorUserInfoKey const ALTUnderlyingErrorCodeErrorKey = @"underlyingErrorCode";
|
NSErrorUserInfoKey const ALTUnderlyingErrorCodeErrorKey = @"underlyingErrorCode";
|
||||||
@@ -24,8 +31,16 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
|
|
||||||
+ (void)load
|
+ (void)load
|
||||||
{
|
{
|
||||||
[NSError setUserInfoValueProviderForDomain:AltServerErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) {
|
[NSError alt_setUserInfoValueProviderForDomain:AltServerErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) {
|
||||||
if ([userInfoKey isEqualToString:NSLocalizedFailureReasonErrorKey])
|
if ([userInfoKey isEqualToString:NSLocalizedDescriptionKey])
|
||||||
|
{
|
||||||
|
return [error altserver_localizedDescription];
|
||||||
|
}
|
||||||
|
else if ([userInfoKey isEqualToString:NSLocalizedFailureErrorKey])
|
||||||
|
{
|
||||||
|
return [error altserver_localizedFailure];
|
||||||
|
}
|
||||||
|
else if ([userInfoKey isEqualToString:NSLocalizedFailureReasonErrorKey])
|
||||||
{
|
{
|
||||||
return [error altserver_localizedFailureReason];
|
return [error altserver_localizedFailureReason];
|
||||||
}
|
}
|
||||||
@@ -41,10 +56,10 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
return nil;
|
return nil;
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[NSError setUserInfoValueProviderForDomain:AltServerConnectionErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) {
|
[NSError alt_setUserInfoValueProviderForDomain:AltServerConnectionErrorDomain provider:^id _Nullable(NSError * _Nonnull error, NSErrorUserInfoKey _Nonnull userInfoKey) {
|
||||||
if ([userInfoKey isEqualToString:NSLocalizedDescriptionKey])
|
if ([userInfoKey isEqualToString:NSLocalizedFailureReasonErrorKey])
|
||||||
{
|
{
|
||||||
return [error altserver_connection_localizedDescription];
|
return [error altserver_connection_localizedFailureReason];
|
||||||
}
|
}
|
||||||
else if ([userInfoKey isEqualToString:NSLocalizedRecoverySuggestionErrorKey])
|
else if ([userInfoKey isEqualToString:NSLocalizedRecoverySuggestionErrorKey])
|
||||||
{
|
{
|
||||||
@@ -55,6 +70,53 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (nullable NSString *)altserver_localizedDescription
|
||||||
|
{
|
||||||
|
switch ((ALTServerError)self.code)
|
||||||
|
{
|
||||||
|
case ALTServerErrorUnderlyingError:
|
||||||
|
{
|
||||||
|
// We're wrapping another error, so return the wrapped error's localized description.
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
return underlyingError.localizedDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (nullable NSString *)altserver_localizedFailure
|
||||||
|
{
|
||||||
|
switch ((ALTServerError)self.code)
|
||||||
|
{
|
||||||
|
case ALTServerErrorUnderlyingError:
|
||||||
|
{
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
return underlyingError.alt_localizedFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ALTServerErrorConnectionFailed:
|
||||||
|
{
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
if (underlyingError.localizedFailureReason != nil)
|
||||||
|
{
|
||||||
|
// Only return localized failure if there is an underlying error with failure reason.
|
||||||
|
#if TARGET_OS_OSX
|
||||||
|
return NSLocalizedString(@"There was an error connecting to the device.", @"");
|
||||||
|
#else
|
||||||
|
return NSLocalizedString(@"AltServer could not establish a connection to SideStore.", @"");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (nullable NSString *)altserver_localizedFailureReason
|
- (nullable NSString *)altserver_localizedFailureReason
|
||||||
{
|
{
|
||||||
switch ((ALTServerError)self.code)
|
switch ((ALTServerError)self.code)
|
||||||
@@ -80,12 +142,21 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
return NSLocalizedString(@"An unknown error occured.", @"");
|
return NSLocalizedString(@"An unknown error occured.", @"");
|
||||||
|
|
||||||
case ALTServerErrorConnectionFailed:
|
case ALTServerErrorConnectionFailed:
|
||||||
|
{
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
if (underlyingError.localizedFailureReason != nil)
|
||||||
|
{
|
||||||
|
return underlyingError.localizedFailureReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return fallback failure reason if there isn't an underlying error with failure reason.
|
||||||
#if TARGET_OS_OSX
|
#if TARGET_OS_OSX
|
||||||
return NSLocalizedString(@"There was an error connecting to the device.", @"");
|
return NSLocalizedString(@"There was an error connecting to the device.", @"");
|
||||||
#else
|
#else
|
||||||
return NSLocalizedString(@"Could not connect to SideStore.", @"");
|
return NSLocalizedString(@"Could not connect to SideStore.", @"");
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
case ALTServerErrorLostConnection:
|
case ALTServerErrorLostConnection:
|
||||||
return NSLocalizedString(@"Lost connection to SideStore.", @"");
|
return NSLocalizedString(@"Lost connection to SideStore.", @"");
|
||||||
|
|
||||||
@@ -93,8 +164,8 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
return NSLocalizedString(@"SideStore could not find this device.", @"");
|
return NSLocalizedString(@"SideStore could not find this device.", @"");
|
||||||
|
|
||||||
case ALTServerErrorDeviceWriteFailed:
|
case ALTServerErrorDeviceWriteFailed:
|
||||||
return NSLocalizedString(@"Failed to write app data to device.", @"");
|
return NSLocalizedString(@"SideStore could not write data to this device.", @"");
|
||||||
|
|
||||||
case ALTServerErrorInvalidRequest:
|
case ALTServerErrorInvalidRequest:
|
||||||
return NSLocalizedString(@"SideStore received an invalid request.", @"");
|
return NSLocalizedString(@"SideStore received an invalid request.", @"");
|
||||||
|
|
||||||
@@ -102,14 +173,20 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
return NSLocalizedString(@"SideStore sent an invalid response.", @"");
|
return NSLocalizedString(@"SideStore sent an invalid response.", @"");
|
||||||
|
|
||||||
case ALTServerErrorInvalidApp:
|
case ALTServerErrorInvalidApp:
|
||||||
return NSLocalizedString(@"The app is invalid.", @"");
|
return NSLocalizedString(@"The app is in an invalid format.", @"");
|
||||||
|
|
||||||
case ALTServerErrorInstallationFailed:
|
case ALTServerErrorInstallationFailed:
|
||||||
|
{
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
if (underlyingError != nil) {
|
||||||
|
return underlyingError.localizedFailureReason ?: underlyingError.localizedDescription;
|
||||||
|
}
|
||||||
return NSLocalizedString(@"An error occured while installing the app.", @"");
|
return NSLocalizedString(@"An error occured while installing the app.", @"");
|
||||||
|
}
|
||||||
|
|
||||||
case ALTServerErrorMaximumFreeAppLimitReached:
|
case ALTServerErrorMaximumFreeAppLimitReached:
|
||||||
return NSLocalizedString(@"Cannot activate more than 3 apps with a non-developer Apple ID.", @"");
|
return NSLocalizedString(@"You cannot activate more than 3 apps with a non-developer Apple ID.", @"");
|
||||||
|
|
||||||
case ALTServerErrorUnsupportediOSVersion:
|
case ALTServerErrorUnsupportediOSVersion:
|
||||||
return NSLocalizedString(@"Your device must be running iOS 12.2 or later to install SideStore.", @"");
|
return NSLocalizedString(@"Your device must be running iOS 12.2 or later to install SideStore.", @"");
|
||||||
|
|
||||||
@@ -117,8 +194,8 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
return NSLocalizedString(@"SideStore does not support this request.", @"");
|
return NSLocalizedString(@"SideStore does not support this request.", @"");
|
||||||
|
|
||||||
case ALTServerErrorUnknownResponse:
|
case ALTServerErrorUnknownResponse:
|
||||||
return NSLocalizedString(@"Received an unknown response from SideStore.", @"");
|
return NSLocalizedString(@"SideStore received an unknown response from SideStore.", @"");
|
||||||
|
|
||||||
case ALTServerErrorInvalidAnisetteData:
|
case ALTServerErrorInvalidAnisetteData:
|
||||||
return NSLocalizedString(@"The provided anisette data is invalid.", @"");
|
return NSLocalizedString(@"The provided anisette data is invalid.", @"");
|
||||||
|
|
||||||
@@ -153,7 +230,19 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
{
|
{
|
||||||
switch ((ALTServerError)self.code)
|
switch ((ALTServerError)self.code)
|
||||||
{
|
{
|
||||||
|
case ALTServerErrorUnderlyingError:
|
||||||
|
{
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
return underlyingError.localizedRecoverySuggestion;
|
||||||
|
}
|
||||||
case ALTServerErrorConnectionFailed:
|
case ALTServerErrorConnectionFailed:
|
||||||
|
{
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
if (underlyingError.localizedRecoverySuggestion != nil){
|
||||||
|
return underlyingError.localizedRecoverySuggestion;
|
||||||
|
}
|
||||||
|
// If there is no underlying error found, fall through to AltServerErrorDeviceNotFound
|
||||||
|
}
|
||||||
case ALTServerErrorDeviceNotFound:
|
case ALTServerErrorDeviceNotFound:
|
||||||
return NSLocalizedString(@"Make sure you have trusted this device with your computer and Wi-Fi sync is enabled.", @"");
|
return NSLocalizedString(@"Make sure you have trusted this device with your computer and Wi-Fi sync is enabled.", @"");
|
||||||
|
|
||||||
@@ -182,6 +271,13 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
{
|
{
|
||||||
switch ((ALTServerError)self.code)
|
switch ((ALTServerError)self.code)
|
||||||
{
|
{
|
||||||
|
case ALTServerErrorUnderlyingError:
|
||||||
|
{
|
||||||
|
NSError *underlyingError = self.userInfo[NSUnderlyingErrorKey];
|
||||||
|
return underlyingError.alt_localizedDebugDescription;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
case ALTServerErrorIncompatibleDeveloperDisk:
|
case ALTServerErrorIncompatibleDeveloperDisk:
|
||||||
{
|
{
|
||||||
NSString *path = self.userInfo[NSFilePathErrorKey];
|
NSString *path = self.userInfo[NSFilePathErrorKey];
|
||||||
@@ -191,7 +287,7 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSString *osVersion = [self altserver_osVersion] ?: NSLocalizedString(@"this device's OS version", @"");
|
NSString *osVersion = [self altserver_osVersion] ?: NSLocalizedString(@"this device's OS version", @"");
|
||||||
NSString *debugDescription = [NSString stringWithFormat:NSLocalizedString(@"The Developer disk located at\n\n%@\n\nis incompatible with %@.", @""), path, osVersion];
|
NSString *debugDescription = [NSString stringWithFormat:NSLocalizedString(@"The Developer disk located at %@ is incompatible with %@.", @""), path, osVersion];
|
||||||
return debugDescription;
|
return debugDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +328,7 @@ NSErrorUserInfoKey const ALTOperatingSystemVersionErrorKey = @"ALTOperatingSyste
|
|||||||
|
|
||||||
#pragma mark - AltServerConnectionErrorDomain -
|
#pragma mark - AltServerConnectionErrorDomain -
|
||||||
|
|
||||||
- (nullable NSString *)altserver_connection_localizedDescription
|
- (nullable NSString *)altserver_connection_localizedFailureReason
|
||||||
{
|
{
|
||||||
switch ((ALTServerConnectionError)self.code)
|
switch ((ALTServerConnectionError)self.code)
|
||||||
{
|
{
|
||||||
|
|||||||
182
Shared/Errors/ALTLocalizedError.swift
Normal file
182
Shared/Errors/ALTLocalizedError.swift
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
//
|
||||||
|
// ALTLocalizedError.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 10/14/22.
|
||||||
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AltSign
|
||||||
|
|
||||||
|
public let ALTLocalizedTitleErrorKey = "ALTLocalizedTitle"
|
||||||
|
public let ALTLocalizedDescriptionKey = "ALTLocalizedDescription"
|
||||||
|
|
||||||
|
public protocol ALTLocalizedError<Code>: LocalizedError, CustomNSError, CustomStringConvertible
|
||||||
|
{
|
||||||
|
associatedtype Code: ALTErrorCode
|
||||||
|
|
||||||
|
var code: Code { get }
|
||||||
|
var errorFailureReason: String { get }
|
||||||
|
|
||||||
|
var errorTitle: String? { get set }
|
||||||
|
var errorFailure: String? { get set }
|
||||||
|
|
||||||
|
var sourceFile: String? { get set }
|
||||||
|
var sourceLine: UInt? { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension ALTLocalizedError
|
||||||
|
{
|
||||||
|
var sourceFile: String? {
|
||||||
|
get { nil }
|
||||||
|
set {}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceLine: UInt? {
|
||||||
|
get { nil }
|
||||||
|
set {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol ALTErrorCode: RawRepresentable where RawValue == Int
|
||||||
|
{
|
||||||
|
associatedtype Error: ALTLocalizedError where Error.Code == Self
|
||||||
|
|
||||||
|
static var errorDomain: String { get } // Optional
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol ALTErrorEnum: ALTErrorCode
|
||||||
|
{
|
||||||
|
associatedtype Error = DefaultLocalizedError<Self>
|
||||||
|
|
||||||
|
var errorFailureReason: String { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// LocalizedError & CustomNSError & CustomStringConvertible
|
||||||
|
public extension ALTLocalizedError
|
||||||
|
{
|
||||||
|
var errorCode: Int { self.code.rawValue }
|
||||||
|
|
||||||
|
var errorDescription: String? {
|
||||||
|
guard (self as NSError).localizedFailure == nil else {
|
||||||
|
// Error has localizedFailure, so return nil to construct localizedDescription from it + localizedFailureReason.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, return failureReason for localizedDescription to avoid system prepending "Operation Failed" message.
|
||||||
|
return self.failureReason
|
||||||
|
}
|
||||||
|
|
||||||
|
var failureReason: String? {
|
||||||
|
return self.errorFailureReason
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorUserInfo: [String : Any] {
|
||||||
|
let userInfo: [String: Any?] = [
|
||||||
|
NSLocalizedFailureErrorKey: self.errorFailure,
|
||||||
|
ALTLocalizedTitleErrorKey: self.errorTitle,
|
||||||
|
// ALTSourceFileErrorKey: self.sourceFile, // TODO: Figure out where these come from
|
||||||
|
// ALTSourceLineErrorKey: self.sourceLine,
|
||||||
|
]
|
||||||
|
|
||||||
|
return userInfo.compactMapValues { $0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
var description: String {
|
||||||
|
let description = "\(self.localizedErrorCode) “\(self.localizedDescription)”"
|
||||||
|
return description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default Implementations
|
||||||
|
public extension ALTLocalizedError where Code: ALTErrorEnum
|
||||||
|
{
|
||||||
|
static var errorDomain: String {
|
||||||
|
return Code.errorDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALTErrorEnum Codes provide their failure reason directly.
|
||||||
|
var errorFailureReason: String {
|
||||||
|
return self.code.errorFailureReason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default Implementations
|
||||||
|
public extension ALTErrorCode
|
||||||
|
{
|
||||||
|
static var errorDomain: String {
|
||||||
|
let typeName = String(reflecting: Self.self) // "\(Self.self)" doesn't include module name, but String(reflecting:) does.
|
||||||
|
let errorDomain = typeName.replacingOccurrences(of: "ErrorCode", with: "Error")
|
||||||
|
return errorDomain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension ALTLocalizedError
|
||||||
|
{
|
||||||
|
// Allows us to initialize errors with localizedTitle + localizedFailure
|
||||||
|
// while still using the error's custom initializer at callsite.
|
||||||
|
init(_ error: Self, localizedTitle: String? = nil, localizedFailure: String? = nil)
|
||||||
|
{
|
||||||
|
self = error
|
||||||
|
|
||||||
|
if let localizedTitle
|
||||||
|
{
|
||||||
|
self.errorTitle = localizedTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
if let localizedFailure
|
||||||
|
{
|
||||||
|
self.errorFailure = localizedFailure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DefaultLocalizedError<Code: ALTErrorEnum>: ALTLocalizedError
|
||||||
|
{
|
||||||
|
public let code: Code
|
||||||
|
|
||||||
|
public var errorTitle: String?
|
||||||
|
public var errorFailure: String?
|
||||||
|
public var sourceFile: String?
|
||||||
|
public var sourceLine: UInt?
|
||||||
|
|
||||||
|
public init(_ code: Code, localizedTitle: String? = nil, localizedFailure: String? = nil, sourceFile: String? = #fileID, sourceLine: UInt? = #line)
|
||||||
|
{
|
||||||
|
self.code = code
|
||||||
|
self.errorTitle = localizedTitle
|
||||||
|
self.errorFailure = localizedFailure
|
||||||
|
self.sourceFile = sourceFile
|
||||||
|
self.sourceLine = sourceLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Custom Operators
|
||||||
|
/// These allow us to pattern match ALTErrorCodes against arbitrary errors via ~ prefix.
|
||||||
|
prefix operator ~
|
||||||
|
public prefix func ~<Code: ALTErrorCode>(expression: Code) -> NSError
|
||||||
|
{
|
||||||
|
let nsError = NSError(domain: Code.errorDomain, code: expression.rawValue)
|
||||||
|
return nsError
|
||||||
|
}
|
||||||
|
|
||||||
|
public func ~=(pattern: any Swift.Error, value: any Swift.Error) -> Bool
|
||||||
|
{
|
||||||
|
let isMatch = pattern._domain == value._domain && pattern._code == value._code
|
||||||
|
return isMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
// These operators *should* allow us to match ALTErrorCodes against arbitrary errors,
|
||||||
|
// but they don't work as of iOS 16.1 and Swift 5.7.
|
||||||
|
//
|
||||||
|
//public func ~=<Error: ALTLocalizedError>(pattern: Error, value: Swift.Error) -> Bool
|
||||||
|
//{
|
||||||
|
// let isMatch = pattern._domain == value._domain && pattern._code == value._code
|
||||||
|
// return isMatch
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//public func ~=<Code: ALTErrorCode>(pattern: Code, value: Swift.Error) -> Bool
|
||||||
|
//{
|
||||||
|
// let isMatch = Code.errorDomain == value._domain && pattern.rawValue == value._code
|
||||||
|
// return isMatch
|
||||||
|
//}
|
||||||
25
Shared/Errors/ALTWrappedError.h
Normal file
25
Shared/Errors/ALTWrappedError.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// ALTWrappedError.h
|
||||||
|
// AltStoreCore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 11/28/22.
|
||||||
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
// Overrides localizedDescription to check userInfoValueProvider for failure reason
|
||||||
|
// instead of default behavior which just returns NSLocalizedFailureErrorKey if present.
|
||||||
|
//
|
||||||
|
// Must be written in Objective-C for Swift.Error <-> NSError bridging to work correctly.
|
||||||
|
@interface ALTWrappedError : NSError
|
||||||
|
|
||||||
|
@property (copy, nonatomic) NSError *wrappedError;
|
||||||
|
|
||||||
|
- (instancetype)initWithError:(NSError *)error userInfo:(NSDictionary<NSString *, id> *)userInfo;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
73
Shared/Errors/ALTWrappedError.m
Normal file
73
Shared/Errors/ALTWrappedError.m
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
//
|
||||||
|
// ALTWrappedError.m
|
||||||
|
// AltStoreCore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 11/28/22.
|
||||||
|
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "ALTWrappedError.h"
|
||||||
|
|
||||||
|
@implementation ALTWrappedError
|
||||||
|
|
||||||
|
+ (BOOL)supportsSecureCoding
|
||||||
|
{
|
||||||
|
// Required in order to serialize errors for legacy AltServer communication.
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithError:(NSError *)error userInfo:(NSDictionary<NSString *,id> *)userInfo
|
||||||
|
{
|
||||||
|
self = [super initWithDomain:error.domain code:error.code userInfo:userInfo];
|
||||||
|
if (self)
|
||||||
|
{
|
||||||
|
if ([error isKindOfClass:[ALTWrappedError class]])
|
||||||
|
{
|
||||||
|
_wrappedError = [(ALTWrappedError *)error wrappedError];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_wrappedError = [error copy];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)localizedDescription
|
||||||
|
{
|
||||||
|
NSString *localizedFailure = self.userInfo[NSLocalizedFailureErrorKey];
|
||||||
|
if (localizedFailure != nil)
|
||||||
|
{
|
||||||
|
NSString *wrappedLocalizedDescription = self.wrappedError.userInfo[NSLocalizedDescriptionKey];
|
||||||
|
NSString *localizedFailureReason = wrappedLocalizedDescription ?: self.wrappedError.localizedFailureReason ?: self.wrappedError.localizedDescription;
|
||||||
|
|
||||||
|
NSString *localizedDescription = [NSString stringWithFormat:@"%@ %@", localizedFailure, localizedFailureReason];
|
||||||
|
return localizedDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
// localizedFailure is nil, so return wrappedError's localizedDescription.
|
||||||
|
return self.wrappedError.localizedDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)localizedFailureReason
|
||||||
|
{
|
||||||
|
return self.wrappedError.localizedFailureReason;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)localizedRecoverySuggestion
|
||||||
|
{
|
||||||
|
return self.wrappedError.localizedRecoverySuggestion;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)debugDescription
|
||||||
|
{
|
||||||
|
return self.wrappedError.debugDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)helpAnchor
|
||||||
|
{
|
||||||
|
return self.wrappedError.helpAnchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -22,12 +22,8 @@ public extension ALTServerError
|
|||||||
case is EncodingError: self = ALTServerError(.invalidResponse, underlyingError: error)
|
case is EncodingError: self = ALTServerError(.invalidResponse, underlyingError: error)
|
||||||
case let error as NSError:
|
case let error as NSError:
|
||||||
var userInfo = error.userInfo
|
var userInfo = error.userInfo
|
||||||
if !userInfo.keys.contains(NSUnderlyingErrorKey)
|
userInfo[NSUnderlyingErrorKey] = error
|
||||||
{
|
|
||||||
// Assign underlying error (if there isn't already one).
|
|
||||||
userInfo[NSUnderlyingErrorKey] = error
|
|
||||||
}
|
|
||||||
|
|
||||||
self = ALTServerError(.underlyingError, userInfo: userInfo)
|
self = ALTServerError(.underlyingError, userInfo: userInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,17 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension NSError
|
#if canImport(UIKit)
|
||||||
|
import UIKit
|
||||||
|
public typealias ALTFont = UIFont
|
||||||
|
#elseif canImport(AppKit)
|
||||||
|
import AppKit
|
||||||
|
public typealias ALTFont = NSFont
|
||||||
|
#endif
|
||||||
|
|
||||||
|
import AltSign
|
||||||
|
|
||||||
|
public extension NSError
|
||||||
{
|
{
|
||||||
@objc(alt_localizedFailure)
|
@objc(alt_localizedFailure)
|
||||||
var localizedFailure: String? {
|
var localizedFailure: String? {
|
||||||
@@ -21,52 +31,52 @@ extension NSError
|
|||||||
let debugDescription = (self.userInfo[NSDebugDescriptionErrorKey] as? String) ?? (NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSDebugDescriptionErrorKey) as? String)
|
let debugDescription = (self.userInfo[NSDebugDescriptionErrorKey] as? String) ?? (NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSDebugDescriptionErrorKey) as? String)
|
||||||
return debugDescription
|
return debugDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc(alt_localizedTitle)
|
||||||
|
var localizedTitle: String? {
|
||||||
|
return self.userInfo[ALTLocalizedTitleErrorKey] as? String
|
||||||
|
}
|
||||||
|
|
||||||
@objc(alt_errorWithLocalizedFailure:)
|
@objc(alt_errorWithLocalizedFailure:)
|
||||||
func withLocalizedFailure(_ failure: String) -> NSError
|
func withLocalizedFailure(_ failure: String) -> NSError
|
||||||
{
|
{
|
||||||
var userInfo = self.userInfo
|
switch self
|
||||||
userInfo[NSLocalizedFailureErrorKey] = failure
|
|
||||||
|
|
||||||
if let failureReason = self.localizedFailureReason
|
|
||||||
{
|
{
|
||||||
userInfo[NSLocalizedFailureReasonErrorKey] = failureReason
|
case var error as any ALTLocalizedError:
|
||||||
|
error.errorFailure = failure
|
||||||
|
return error as NSError
|
||||||
|
|
||||||
|
default:
|
||||||
|
var userInfo = self.userInfo
|
||||||
|
userInfo[NSLocalizedFailureReasonErrorKey] = failure
|
||||||
|
|
||||||
|
return ALTWrappedError(error: self, userInfo: userInfo)
|
||||||
}
|
}
|
||||||
else if self.localizedFailure == nil && self.localizedFailureReason == nil && self.localizedDescription.contains(self.localizedErrorCode)
|
}
|
||||||
|
@objc(alt_errorWithLocalizedTitle:)
|
||||||
|
func withLocalizedTitle(_ title: String) -> NSError {
|
||||||
|
switch self
|
||||||
{
|
{
|
||||||
// Default localizedDescription, so replace with just the localized error code portion.
|
case var error as any ALTLocalizedError:
|
||||||
userInfo[NSLocalizedFailureReasonErrorKey] = "(\(self.localizedErrorCode).)"
|
error.errorTitle = title
|
||||||
|
return error as NSError
|
||||||
|
|
||||||
|
default:
|
||||||
|
var userInfo = self.userInfo
|
||||||
|
userInfo[ALTLocalizedTitleErrorKey] = title
|
||||||
|
return ALTWrappedError(error: self, userInfo: userInfo)
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
if let localizedDescription = NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSLocalizedDescriptionKey) as? String
|
|
||||||
{
|
|
||||||
userInfo[NSLocalizedDescriptionKey] = localizedDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't accidentally remove localizedDescription from dictionary
|
|
||||||
// userInfo[NSLocalizedDescriptionKey] = NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSLocalizedDescriptionKey) as? String
|
|
||||||
|
|
||||||
if let recoverySuggestion = self.localizedRecoverySuggestion
|
|
||||||
{
|
|
||||||
userInfo[NSLocalizedRecoverySuggestionErrorKey] = recoverySuggestion
|
|
||||||
}
|
|
||||||
|
|
||||||
let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo)
|
|
||||||
return error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sanitizedForCoreData() -> NSError
|
func sanitizedForSerialization() -> NSError
|
||||||
{
|
{
|
||||||
var userInfo = self.userInfo
|
var userInfo = self.userInfo
|
||||||
userInfo[NSLocalizedFailureErrorKey] = self.localizedFailure
|
|
||||||
userInfo[NSLocalizedDescriptionKey] = self.localizedDescription
|
userInfo[NSLocalizedDescriptionKey] = self.localizedDescription
|
||||||
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
|
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
|
||||||
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion
|
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion
|
||||||
|
userInfo[NSDebugDescriptionErrorKey] = self.localizedDebugDescription
|
||||||
|
|
||||||
// Remove userInfo values that don't conform to NSSecureEncoding.
|
// Remove userInfo values that don't conform to NSSecureEncoding.
|
||||||
userInfo = userInfo.filter { (key, value) in
|
userInfo = userInfo.filter { (key, value) in
|
||||||
return (value as AnyObject) is NSSecureCoding
|
return (value as AnyObject) is NSSecureCoding
|
||||||
@@ -75,69 +85,165 @@ extension NSError
|
|||||||
// Sanitize underlying errors.
|
// Sanitize underlying errors.
|
||||||
if let underlyingError = userInfo[NSUnderlyingErrorKey] as? Error
|
if let underlyingError = userInfo[NSUnderlyingErrorKey] as? Error
|
||||||
{
|
{
|
||||||
let sanitizedError = (underlyingError as NSError).sanitizedForCoreData()
|
let sanitizedError = (underlyingError as NSError).sanitizedForSerialization()
|
||||||
userInfo[NSUnderlyingErrorKey] = sanitizedError
|
userInfo[NSUnderlyingErrorKey] = sanitizedError
|
||||||
}
|
}
|
||||||
|
|
||||||
if #available(iOS 14.5, macOS 11.3, *), let underlyingErrors = userInfo[NSMultipleUnderlyingErrorsKey] as? [Error]
|
if #available(iOS 14.5, macOS 11.3, *), let underlyingErrors = userInfo[NSMultipleUnderlyingErrorsKey] as? [Error]
|
||||||
{
|
{
|
||||||
let sanitizedErrors = underlyingErrors.map { ($0 as NSError).sanitizedForCoreData() }
|
let sanitizedErrors = underlyingErrors.map { ($0 as NSError).sanitizedForSerialization() }
|
||||||
userInfo[NSMultipleUnderlyingErrorsKey] = sanitizedErrors
|
userInfo[NSMultipleUnderlyingErrorsKey] = sanitizedErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo)
|
let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo)
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
|
func formattedDetailedDescription(with font: ALTFont) -> NSAttributedString {
|
||||||
|
#if canImport(UIKit)
|
||||||
|
let boldFontDescriptor = font.fontDescriptor.withSymbolicTraits(.traitBold) ?? font.fontDescriptor
|
||||||
|
#else
|
||||||
|
let boldFontDescriptor = font.fontDescriptor.withSymbolicTraits(.bold)
|
||||||
|
#endif
|
||||||
|
let boldFont = ALTFont(descriptor: boldFontDescriptor, size: font.pointSize) ?? font
|
||||||
|
|
||||||
|
var preferredKeyOrder = [
|
||||||
|
NSDebugDescriptionErrorKey,
|
||||||
|
NSLocalizedDescriptionKey,
|
||||||
|
NSLocalizedFailureErrorKey,
|
||||||
|
NSLocalizedFailureReasonErrorKey,
|
||||||
|
NSLocalizedRecoverySuggestionErrorKey,
|
||||||
|
ALTLocalizedTitleErrorKey,
|
||||||
|
// ALTSourceFileErrorKey,
|
||||||
|
// ALTSourceLineErrorKey,
|
||||||
|
NSUnderlyingErrorKey
|
||||||
|
]
|
||||||
|
|
||||||
|
if #available(iOS 14.5, macOS 11.3, *) {
|
||||||
|
preferredKeyOrder.append(NSMultipleUnderlyingErrorsKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userInfo = self.userInfo
|
||||||
|
userInfo[NSDebugDescriptionErrorKey] = self.localizedDebugDescription
|
||||||
|
userInfo[NSLocalizedFailureErrorKey] = self.localizedFailure
|
||||||
|
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
|
||||||
|
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion
|
||||||
|
|
||||||
|
let sortedUserInfo = userInfo.sorted { a, b in
|
||||||
|
let indexA = preferredKeyOrder.firstIndex(of: a.key)
|
||||||
|
let indexB = preferredKeyOrder.firstIndex(of: b.key)
|
||||||
|
switch (indexA, indexB) {
|
||||||
|
case (let indexA?, let indexB?): return indexA < indexB
|
||||||
|
case (_?, nil): return true // indexA exists, indexB is nil, indexA should come first
|
||||||
|
case (nil, _?): return false // indexB exists, indexB is nil, indexB should come first
|
||||||
|
case (nil, nil): return a.key < b.key // both nil, so sort alphabetically
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let detailedDescription = NSMutableAttributedString()
|
||||||
|
|
||||||
|
for (key, value) in sortedUserInfo
|
||||||
|
{
|
||||||
|
let keyName: String
|
||||||
|
switch key
|
||||||
|
{
|
||||||
|
case NSDebugDescriptionErrorKey: keyName = NSLocalizedString("Debug Description", comment: "")
|
||||||
|
case NSLocalizedDescriptionKey: keyName = NSLocalizedString("Error Description", comment: "")
|
||||||
|
case NSLocalizedFailureErrorKey: keyName = NSLocalizedString("Failure", comment: "")
|
||||||
|
case NSLocalizedFailureReasonErrorKey: keyName = NSLocalizedString("Failure Reason", comment: "")
|
||||||
|
case NSLocalizedRecoverySuggestionErrorKey: keyName = NSLocalizedString("Recovery Suggestion", comment: "")
|
||||||
|
case ALTLocalizedTitleErrorKey: keyName = NSLocalizedString("Title", comment: "")
|
||||||
|
// case ALTSourceFileErrorKey: keyName = NSLocalizedString("Source File", comment: "")
|
||||||
|
// case ALTSourceLineErrorKey: keyName = NSLocalizedString("Source Line", comment: "")
|
||||||
|
case NSUnderlyingErrorKey: keyName = NSLocalizedString("Underlying Error", comment: "")
|
||||||
|
default:
|
||||||
|
if #available(iOS 14.5, macOS 11.3, *), key == NSMultipleUnderlyingErrorsKey
|
||||||
|
{
|
||||||
|
keyName = NSLocalizedString("Underlying Errors", comment: "")
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
keyName = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let attributedKey = NSAttributedString(string: keyName, attributes: [.font: boldFont])
|
||||||
|
let attributedValue = NSAttributedString(string: String(describing: value), attributes: [.font: font])
|
||||||
|
|
||||||
|
let attributedString = NSMutableAttributedString(attributedString: attributedKey)
|
||||||
|
attributedString.mutableString.append("\n")
|
||||||
|
attributedString.append(attributedValue)
|
||||||
|
|
||||||
|
if !detailedDescription.string.isEmpty
|
||||||
|
{
|
||||||
|
detailedDescription.mutableString.append("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
detailedDescription.append(attributedString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Support dark mode
|
||||||
|
#if canImport(UIKit)
|
||||||
|
if #available(iOS 13, *)
|
||||||
|
{
|
||||||
|
detailedDescription.addAttribute(.foregroundColor, value: UIColor.label, range: NSMakeRange(0, detailedDescription.length))
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
detailedDescription.addAttribute(.foregroundColor, value: NSColor.labelColor, range: NSMakeRange(0, detailedDescription.length))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return detailedDescription
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Error
|
public extension NSError
|
||||||
|
{
|
||||||
|
typealias UserInfoProvider = (Error, String) -> Any?
|
||||||
|
|
||||||
|
@objc
|
||||||
|
class func alt_setUserInfoValueProvider(forDomain domain: String, provider: UserInfoProvider?) {
|
||||||
|
NSError.setUserInfoValueProvider(forDomain: domain) { error, key in
|
||||||
|
let nsError = error as NSError
|
||||||
|
|
||||||
|
switch key{
|
||||||
|
case NSLocalizedDescriptionKey:
|
||||||
|
if nsError.localizedFailure != nil {
|
||||||
|
// Error has localizedFailure, so return nil to construct localizedDescription from it + localizedFailureReason
|
||||||
|
return nil
|
||||||
|
} else if let localizedDescription = provider?(error, NSLocalizedDescriptionKey) as? String {
|
||||||
|
// Only call provider() if there is no localizedFailure
|
||||||
|
return localizedDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise return failureReason for localizedDescription to avoid system prepending "Operation Failed" message
|
||||||
|
Do NOT return provider(NSLocalizedFailureReason) which might be unexpectedly nil if unrecognized error code. */
|
||||||
|
|
||||||
|
return nsError.localizedFailureReason
|
||||||
|
|
||||||
|
default:
|
||||||
|
return provider?(error, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Error
|
||||||
{
|
{
|
||||||
var underlyingError: Error? {
|
var underlyingError: Error? {
|
||||||
let underlyingError = (self as NSError).userInfo[NSUnderlyingErrorKey] as? Error
|
return (self as NSError).userInfo[NSUnderlyingErrorKey] as? Error
|
||||||
return underlyingError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var localizedErrorCode: String {
|
var localizedErrorCode: String {
|
||||||
let localizedErrorCode = String(format: NSLocalizedString("%@ error %@", comment: ""), (self as NSError).domain, (self as NSError).code as NSNumber)
|
return String(format: NSLocalizedString("%@ %@", comment: ""), (self as NSError).domain, self.displayCode as NSNumber)
|
||||||
return localizedErrorCode
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protocol ALTLocalizedError: LocalizedError, CustomNSError
|
var displayCode: Int {
|
||||||
{
|
guard let serverError = self as? ALTServerError else {
|
||||||
var failure: String? { get }
|
return (self as NSError).code
|
||||||
|
}
|
||||||
var underlyingError: Error? { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ALTLocalizedError
|
/* We want AltServerError codes to start at 2000, but we can't change them without breaking AltServer compatibility.
|
||||||
{
|
Instead we just add 2000 when displaying code to the user to make it appear as if the codes start at 2000 anyway.
|
||||||
var errorUserInfo: [String : Any] {
|
*/
|
||||||
let userInfo = ([
|
return 2000 + serverError.code.rawValue
|
||||||
NSLocalizedDescriptionKey: self.errorDescription,
|
|
||||||
NSLocalizedFailureReasonErrorKey: self.failureReason,
|
|
||||||
NSLocalizedFailureErrorKey: self.failure,
|
|
||||||
NSUnderlyingErrorKey: self.underlyingError
|
|
||||||
] as [String: Any?]).compactMapValues { $0 }
|
|
||||||
return userInfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var underlyingError: Error? {
|
|
||||||
// Error's default implementation calls errorUserInfo,
|
|
||||||
// but ALTLocalizedError.errorUserInfo calls underlyingError.
|
|
||||||
// Return nil to prevent infinite recursion.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var errorDescription: String? {
|
|
||||||
guard let errorFailure = self.failure else { return (self.underlyingError as NSError?)?.localizedDescription }
|
|
||||||
guard let failureReason = self.failureReason else { return errorFailure }
|
|
||||||
|
|
||||||
let errorDescription = errorFailure + " " + failureReason
|
|
||||||
return errorDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
var failureReason: String? { (self.underlyingError as NSError?)?.localizedDescription }
|
|
||||||
var recoverySuggestion: String? { (self.underlyingError as NSError?)?.localizedRecoverySuggestion }
|
|
||||||
var helpAnchor: String? { (self.underlyingError as NSError?)?.helpAnchor }
|
|
||||||
}
|
}
|
||||||
|
|||||||
249
Shared/Server Protocol/CodableError.swift
Normal file
249
Shared/Server Protocol/CodableError.swift
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
//
|
||||||
|
// CodableError.swift
|
||||||
|
// AltKit
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 3/5/20.
|
||||||
|
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
// Can only automatically conform ALTServerError.Code to Codable, not ALTServerError itself
|
||||||
|
extension ALTServerError.Code: Codable {}
|
||||||
|
|
||||||
|
private extension ErrorUserInfoKey
|
||||||
|
{
|
||||||
|
static let altLocalizedDescription: String = "ALTLocalizedDescription"
|
||||||
|
static let altLocalizedFailureReason: String = "ALTLocalizedFailureReason"
|
||||||
|
static let altLocalizedRecoverySuggestion: String = "ALTLocalizedRecoverySuggestion"
|
||||||
|
static let altDebugDescription: String = "ALTDebugDescription"
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CodableError
|
||||||
|
{
|
||||||
|
enum UserInfoValue: Codable
|
||||||
|
{
|
||||||
|
case unknown
|
||||||
|
case string(String)
|
||||||
|
case number(Int)
|
||||||
|
case error(NSError)
|
||||||
|
case codableError(CodableError)
|
||||||
|
indirect case array([UserInfoValue])
|
||||||
|
indirect case dictionary([String: UserInfoValue])
|
||||||
|
|
||||||
|
var value: Any? {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .unknown: return nil
|
||||||
|
case .string(let string): return string
|
||||||
|
case .number(let number): return number
|
||||||
|
case .error(let error): return error
|
||||||
|
case .codableError(let error): return error.error
|
||||||
|
case .array(let array): return array.compactMap { $0.value } // .compactMap instead of .map to ensure nil values are removed.
|
||||||
|
case .dictionary(let dictionary): return dictionary.compactMapValues { $0.value } // .compactMapValues instead of .mapValues to ensure nil values are removed.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var codableValue: Codable? {
|
||||||
|
switch self
|
||||||
|
{
|
||||||
|
case .unknown, .string, .number: return self.value as? Codable
|
||||||
|
case .codableError(let error): return error
|
||||||
|
case .error(let nsError):
|
||||||
|
// Ignore error because we don't want to fail completely if error contains invalid user info value.
|
||||||
|
let sanitizedError = nsError.sanitizedForSerialization()
|
||||||
|
let data = try? NSKeyedArchiver.archivedData(withRootObject: sanitizedError, requiringSecureCoding: true)
|
||||||
|
return data
|
||||||
|
|
||||||
|
case .array(let array): return array
|
||||||
|
case .dictionary(let dictionary): return dictionary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ rawValue: Any?)
|
||||||
|
{
|
||||||
|
switch rawValue
|
||||||
|
{
|
||||||
|
case let string as String: self = .string(string)
|
||||||
|
case let number as Int: self = .number(number)
|
||||||
|
case let error as NSError: self = .codableError(CodableError(error: error))
|
||||||
|
case let array as [Any]: self = .array(array.compactMap(UserInfoValue.init))
|
||||||
|
case let dictionary as [String: Any]: self = .dictionary(dictionary.compactMapValues(UserInfoValue.init))
|
||||||
|
default: self = .unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws
|
||||||
|
{
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
|
||||||
|
if
|
||||||
|
let data = try? container.decode(Data.self),
|
||||||
|
let error = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSError.self, from: data)
|
||||||
|
{
|
||||||
|
self = .error(error)
|
||||||
|
}
|
||||||
|
else if let codableError = try? container.decode(CodableError.self)
|
||||||
|
{
|
||||||
|
self = .codableError(codableError)
|
||||||
|
}
|
||||||
|
else if let string = try? container.decode(String.self)
|
||||||
|
{
|
||||||
|
self = .string(string)
|
||||||
|
}
|
||||||
|
else if let number = try? container.decode(Int.self)
|
||||||
|
{
|
||||||
|
self = .number(number)
|
||||||
|
}
|
||||||
|
else if let array = try? container.decode([UserInfoValue].self)
|
||||||
|
{
|
||||||
|
self = .array(array)
|
||||||
|
}
|
||||||
|
else if let dictionary = try? container.decode([String: UserInfoValue].self)
|
||||||
|
{
|
||||||
|
self = .dictionary(dictionary)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self = .unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws
|
||||||
|
{
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
|
||||||
|
if let value = self.codableValue
|
||||||
|
{
|
||||||
|
try container.encode(value)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try container.encodeNil()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CodableError: Codable
|
||||||
|
{
|
||||||
|
var error: Error {
|
||||||
|
return self.rawError ?? NSError(domain: self.errorDomain, code: self.errorCode, userInfo: self.userInfo ?? [:])
|
||||||
|
}
|
||||||
|
private var rawError: Error?
|
||||||
|
|
||||||
|
private var errorDomain: String
|
||||||
|
private var errorCode: Int
|
||||||
|
private var userInfo: [String: Any]?
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey
|
||||||
|
{
|
||||||
|
case errorDomain
|
||||||
|
case errorCode
|
||||||
|
case legacyUserInfo = "userInfo"
|
||||||
|
case errorUserInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
init(error: Error)
|
||||||
|
{
|
||||||
|
self.rawError = error
|
||||||
|
|
||||||
|
let nsError = error as NSError
|
||||||
|
self.errorDomain = nsError.domain
|
||||||
|
self.errorCode = nsError.code
|
||||||
|
|
||||||
|
if !nsError.userInfo.isEmpty
|
||||||
|
{
|
||||||
|
self.userInfo = nsError.userInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws
|
||||||
|
{
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
// Assume ALTServerError.errorDomain if no explicit domain provided.
|
||||||
|
self.errorDomain = try container.decodeIfPresent(String.self, forKey: .errorDomain) ?? ALTServerError.errorDomain
|
||||||
|
self.errorCode = try container.decode(Int.self, forKey: .errorCode)
|
||||||
|
|
||||||
|
if let rawUserInfo = try container.decodeIfPresent([String: UserInfoValue].self, forKey: .errorUserInfo)
|
||||||
|
{
|
||||||
|
// Attempt decoding from .errorUserInfo first, because it will gracefully handle unknown user info values.
|
||||||
|
|
||||||
|
// Copy ALTLocalized... values to NSLocalized... if provider is nil or if error is unrecognized.
|
||||||
|
// This ensures we preserve error messages if receiving an unknown error.
|
||||||
|
var userInfo = rawUserInfo.compactMapValues { $0.value }
|
||||||
|
|
||||||
|
// Recognized == the provider returns value for NSLocalizedFailureReasonErrorKey, or error is ALTServerError.underlyingError.
|
||||||
|
let provider = NSError.userInfoValueProvider(forDomain: self.errorDomain)
|
||||||
|
let isRecognizedError = (
|
||||||
|
provider?(self.error, NSLocalizedFailureReasonErrorKey) != nil ||
|
||||||
|
(self.error._domain == ALTServerError.errorDomain && self.error._code == ALTServerError.underlyingError.rawValue)
|
||||||
|
)
|
||||||
|
|
||||||
|
if !isRecognizedError
|
||||||
|
{
|
||||||
|
// Error not recognized, so copy over NSLocalizedDescriptionKey and NSLocalizedFailureReasonErrorKey.
|
||||||
|
userInfo[NSLocalizedDescriptionKey] = userInfo[ErrorUserInfoKey.altLocalizedDescription]
|
||||||
|
userInfo[NSLocalizedFailureReasonErrorKey] = userInfo[ErrorUserInfoKey.altLocalizedFailureReason]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy over NSLocalizedRecoverySuggestionErrorKey and NSDebugDescriptionErrorKey if provider returns nil.
|
||||||
|
if provider?(self.error, NSLocalizedRecoverySuggestionErrorKey) == nil
|
||||||
|
{
|
||||||
|
userInfo[NSLocalizedRecoverySuggestionErrorKey] = userInfo[ErrorUserInfoKey.altLocalizedRecoverySuggestion]
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider?(self.error, NSDebugDescriptionErrorKey) == nil
|
||||||
|
{
|
||||||
|
userInfo[NSDebugDescriptionErrorKey] = userInfo[ErrorUserInfoKey.altDebugDescription]
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo[ErrorUserInfoKey.altLocalizedDescription] = nil
|
||||||
|
userInfo[ErrorUserInfoKey.altLocalizedFailureReason] = nil
|
||||||
|
userInfo[ErrorUserInfoKey.altLocalizedRecoverySuggestion] = nil
|
||||||
|
userInfo[ErrorUserInfoKey.altDebugDescription] = nil
|
||||||
|
|
||||||
|
self.userInfo = userInfo
|
||||||
|
}
|
||||||
|
else if let rawUserInfo = try container.decodeIfPresent([String: UserInfoValue].self, forKey: .legacyUserInfo)
|
||||||
|
{
|
||||||
|
// Fall back to decoding .legacyUserInfo, which only supports String and NSError values.
|
||||||
|
let userInfo = rawUserInfo.compactMapValues { $0.value }
|
||||||
|
self.userInfo = userInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws
|
||||||
|
{
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
try container.encode(self.errorDomain, forKey: .errorDomain)
|
||||||
|
try container.encode(self.errorCode, forKey: .errorCode)
|
||||||
|
|
||||||
|
let rawLegacyUserInfo = self.userInfo?.compactMapValues { (value) -> UserInfoValue? in
|
||||||
|
// .legacyUserInfo only supports String and NSError values.
|
||||||
|
switch value
|
||||||
|
{
|
||||||
|
case let string as String: return .string(string)
|
||||||
|
case let error as NSError: return .error(error) // Must use .error, not .codableError for backwards compatibility.
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try container.encodeIfPresent(rawLegacyUserInfo, forKey: .legacyUserInfo)
|
||||||
|
|
||||||
|
let nsError = self.error as NSError
|
||||||
|
|
||||||
|
var userInfo = self.userInfo ?? [:]
|
||||||
|
userInfo[ErrorUserInfoKey.altLocalizedDescription] = nsError.localizedDescription
|
||||||
|
userInfo[ErrorUserInfoKey.altLocalizedFailureReason] = nsError.localizedFailureReason
|
||||||
|
userInfo[ErrorUserInfoKey.altLocalizedRecoverySuggestion] = nsError.localizedRecoverySuggestion
|
||||||
|
userInfo[ErrorUserInfoKey.altDebugDescription] = nsError.localizedDebugDescription
|
||||||
|
|
||||||
|
// No need to use alternate key. This is a no-op if userInfo already contains localizedFailure,
|
||||||
|
// but it caches the UserInfoProvider value if one exists.
|
||||||
|
userInfo[NSLocalizedFailureErrorKey] = nsError.localizedFailure
|
||||||
|
|
||||||
|
let rawUserInfo = userInfo.compactMapValues { UserInfoValue($0) }
|
||||||
|
try container.encodeIfPresent(rawUserInfo, forKey: .errorUserInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
//
|
|
||||||
// CodableServerError.swift
|
|
||||||
// AltKit
|
|
||||||
//
|
|
||||||
// Created by Riley Testut on 3/5/20.
|
|
||||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
// Can only automatically conform ALTServerError.Code to Codable, not ALTServerError itself
|
|
||||||
extension ALTServerError.Code: Codable {}
|
|
||||||
|
|
||||||
extension CodableServerError
|
|
||||||
{
|
|
||||||
enum UserInfoValue: Codable
|
|
||||||
{
|
|
||||||
case string(String)
|
|
||||||
case error(NSError)
|
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws
|
|
||||||
{
|
|
||||||
let container = try decoder.singleValueContainer()
|
|
||||||
|
|
||||||
if
|
|
||||||
let data = try? container.decode(Data.self),
|
|
||||||
let error = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSError.self, from: data)
|
|
||||||
{
|
|
||||||
self = .error(error)
|
|
||||||
}
|
|
||||||
else if let string = try? container.decode(String.self)
|
|
||||||
{
|
|
||||||
self = .string(string)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "UserInfoValue value cannot be decoded")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func encode(to encoder: Encoder) throws
|
|
||||||
{
|
|
||||||
var container = encoder.singleValueContainer()
|
|
||||||
|
|
||||||
switch self
|
|
||||||
{
|
|
||||||
case .string(let string): try container.encode(string)
|
|
||||||
case .error(let error):
|
|
||||||
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: error, requiringSecureCoding: true) else {
|
|
||||||
let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "UserInfoValue value \(self) cannot be encoded")
|
|
||||||
throw EncodingError.invalidValue(self, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
try container.encode(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CodableServerError: Codable
|
|
||||||
{
|
|
||||||
var error: ALTServerError {
|
|
||||||
return ALTServerError(self.errorCode, userInfo: self.userInfo ?? [:])
|
|
||||||
}
|
|
||||||
|
|
||||||
private var errorCode: ALTServerError.Code
|
|
||||||
private var userInfo: [String: Any]?
|
|
||||||
|
|
||||||
private enum CodingKeys: String, CodingKey
|
|
||||||
{
|
|
||||||
case errorCode
|
|
||||||
case userInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
init(error: ALTServerError)
|
|
||||||
{
|
|
||||||
self.errorCode = error.code
|
|
||||||
|
|
||||||
var userInfo = error.userInfo
|
|
||||||
if let localizedRecoverySuggestion = (error as NSError).localizedRecoverySuggestion
|
|
||||||
{
|
|
||||||
userInfo[NSLocalizedRecoverySuggestionErrorKey] = localizedRecoverySuggestion
|
|
||||||
}
|
|
||||||
|
|
||||||
if !userInfo.isEmpty
|
|
||||||
{
|
|
||||||
self.userInfo = userInfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(from decoder: Decoder) throws
|
|
||||||
{
|
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
||||||
|
|
||||||
let errorCode = try container.decode(Int.self, forKey: .errorCode)
|
|
||||||
self.errorCode = ALTServerError.Code(rawValue: errorCode) ?? .unknown
|
|
||||||
|
|
||||||
let rawUserInfo = try container.decodeIfPresent([String: UserInfoValue].self, forKey: .userInfo)
|
|
||||||
|
|
||||||
let userInfo = rawUserInfo?.mapValues { (value) -> Any in
|
|
||||||
switch value
|
|
||||||
{
|
|
||||||
case .string(let string): return string
|
|
||||||
case .error(let error): return error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.userInfo = userInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func encode(to encoder: Encoder) throws
|
|
||||||
{
|
|
||||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
||||||
try container.encode(self.error.code.rawValue, forKey: .errorCode)
|
|
||||||
|
|
||||||
let rawUserInfo = self.userInfo?.compactMapValues { (value) -> UserInfoValue? in
|
|
||||||
switch value
|
|
||||||
{
|
|
||||||
case let string as String: return .string(string)
|
|
||||||
case let error as NSError: return .error(error)
|
|
||||||
default: return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try container.encodeIfPresent(rawUserInfo, forKey: .userInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -197,20 +197,20 @@ public enum ServerResponse: Decodable
|
|||||||
// from easily changing response format for a request in the future.
|
// from easily changing response format for a request in the future.
|
||||||
public struct ErrorResponse: ServerMessageProtocol
|
public struct ErrorResponse: ServerMessageProtocol
|
||||||
{
|
{
|
||||||
public var version = 2
|
public var version = 3
|
||||||
public var identifier = "ErrorResponse"
|
public var identifier = "ErrorResponse"
|
||||||
|
|
||||||
public var error: ALTServerError {
|
public var error: ALTServerError {
|
||||||
return self.serverError?.error ?? ALTServerError(self.errorCode)
|
return self.serverError.map { ALTServerError($0.error) } ?? ALTServerError(self.errorCode)
|
||||||
}
|
}
|
||||||
private var serverError: CodableServerError?
|
private var serverError: CodableError?
|
||||||
|
|
||||||
// Legacy (v1)
|
// Legacy (v1)
|
||||||
private var errorCode: ALTServerError.Code
|
private var errorCode: ALTServerError.Code
|
||||||
|
|
||||||
public init(error: ALTServerError)
|
public init(error: ALTServerError)
|
||||||
{
|
{
|
||||||
self.serverError = CodableServerError(error: error)
|
self.serverError = CodableError(error: error)
|
||||||
self.errorCode = error.code
|
self.errorCode = error.code
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"identifier": "com.litritt.litsource",
|
"identifier": "com.litritt.litsource",
|
||||||
"sourceURL": "https://apps.litritt.com/"
|
"sourceURL": "https://altstore.ignitedemulator.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "thatstel.la.altsource",
|
||||||
|
"sourceURL": "https://alt.thatstel.la/"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user