Compare commits
95 Commits
spidy/cell
...
a6be43da53
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6be43da53 | ||
|
|
c807154873 | ||
|
|
1103a7f0b4 | ||
|
|
8a63725113 | ||
|
|
4a73efe680 | ||
|
|
be4d62fb1e | ||
|
|
7683447eea | ||
|
|
d18422af00 | ||
|
|
0062374d32 | ||
|
|
d45803a7cc | ||
|
|
661fda4988 | ||
|
|
83ed17c443 | ||
|
|
096bb85bc1 | ||
|
|
786f06800f | ||
|
|
146b69944a | ||
|
|
37cc2cfca6 | ||
|
|
31fe7fed8c | ||
|
|
dbc20391e5 | ||
|
|
aca8193f1c | ||
|
|
fabe093b1d | ||
|
|
277b5b0bd4 | ||
|
|
e1550811eb | ||
|
|
255db2bac0 | ||
|
|
30c03aad42 | ||
|
|
d70c916222 | ||
|
|
7c038568f8 | ||
|
|
6d511de31d | ||
|
|
8730ae42c6 | ||
|
|
7d7a44bc61 | ||
|
|
639753149f | ||
|
|
cc5aeaf099 | ||
|
|
1d642e2ffa | ||
|
|
decf21f177 | ||
|
|
b2f42160f9 | ||
|
|
4f8a70d31e | ||
|
|
bedda8c6a4 | ||
|
|
d042267ff9 | ||
|
|
4cc534c89e | ||
|
|
606140a7be | ||
|
|
55761029a3 | ||
|
|
4e6756d4d5 | ||
|
|
c64b0c99de | ||
|
|
e19b147962 | ||
|
|
85427ecbc6 | ||
|
|
ecf5e2d770 | ||
|
|
2f2df9ca43 | ||
|
|
31520354bd | ||
|
|
25f13faa67 | ||
|
|
dcaddcc219 | ||
|
|
fa1757ff1d | ||
|
|
f0cb86aff4 | ||
|
|
7a34d7ac67 | ||
|
|
cfd9247fdd | ||
|
|
15ffe766d3 | ||
|
|
a0ae0cb2b1 | ||
|
|
dc53f19947 | ||
|
|
baf3594a8e | ||
|
|
1d4666e79e | ||
|
|
b4df06f742 | ||
|
|
d41a6b17d2 | ||
|
|
d98e0dde72 | ||
|
|
f23a7e9c82 | ||
|
|
7ed45c7cb1 | ||
|
|
c319524df6 | ||
|
|
724e8db980 | ||
|
|
669d33183e | ||
|
|
a12b6cd62b | ||
|
|
685d956775 | ||
|
|
00ed6e61be | ||
|
|
7057d59992 | ||
|
|
6d308487c1 | ||
|
|
6c45eb096f | ||
|
|
4b6ffa5d4a | ||
|
|
728b12d004 | ||
|
|
97160569ba | ||
|
|
1b754e137a | ||
|
|
4176b8c83c | ||
|
|
479f877dbf | ||
|
|
4ff643805b | ||
|
|
0e3c3dddfe | ||
|
|
7415fe6204 | ||
|
|
c128c9268b | ||
|
|
131a0289a2 | ||
|
|
116f045e51 | ||
|
|
c68efd2b44 | ||
|
|
e519389780 | ||
|
|
e6135c6518 | ||
|
|
3d444f301d | ||
|
|
7adfd3d3e8 | ||
|
|
7ec6324b62 | ||
|
|
291d7fd8d9 | ||
|
|
b57d279670 | ||
|
|
dc29b65bd5 | ||
|
|
1e64f50ab9 | ||
|
|
c8127fb3b9 |
1007
.github/.obsolete/reusable-build-workflow.yml
vendored
45
.github/workflows/pr.yml
vendored
@@ -54,57 +54,12 @@ jobs:
|
||||
swiftpm-cache-restore-keys: |
|
||||
xcode-cache-sourcedata-
|
||||
|
||||
|
||||
- name: Restore Pods from Cache (Exact match)
|
||||
id: pods-restore
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-${{ hashFiles('Podfile') }}
|
||||
# restore-keys: | # commented out to strictly check cache for this particular podfile
|
||||
# pods-cache-
|
||||
|
||||
- name: Restore Pods from Cache (Last Available)
|
||||
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||
id: pods-restore-recent
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-
|
||||
|
||||
- name: Install CocoaPods
|
||||
# if: ${{ steps.pods-restore.outputs.cache-hit != 'true'}}
|
||||
id: pods-install
|
||||
run: |
|
||||
pod install
|
||||
|
||||
- name: Save Pods to Cache
|
||||
id: save-pods
|
||||
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-${{ hashFiles('Podfile') }}
|
||||
|
||||
- name: List Files and derived data
|
||||
run: |
|
||||
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
||||
ls -la .
|
||||
echo ""
|
||||
|
||||
echo ">>>>>>>>> Pods <<<<<<<<<<"
|
||||
find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||
echo ""
|
||||
|
||||
echo ">>>>>>>>> SideStore <<<<<<<<<<"
|
||||
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||
echo ""
|
||||
|
||||
35
.github/workflows/reusable-sidestore-build.yml
vendored
@@ -67,26 +67,27 @@ jobs:
|
||||
bundle_id_suffix: ${{ inputs.bundle_id_suffix }}
|
||||
secrets: inherit
|
||||
|
||||
tests-build:
|
||||
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
|
||||
needs: shared
|
||||
uses: ./.github/workflows/sidestore-tests-build.yml
|
||||
with:
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
short_commit: ${{ needs.shared.outputs.short-commit }}
|
||||
secrets: inherit
|
||||
# tests-build:
|
||||
# if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
|
||||
# needs: shared
|
||||
# uses: ./.github/workflows/sidestore-tests-build.yml
|
||||
# with:
|
||||
# release_tag: ${{ inputs.release_tag }}
|
||||
# short_commit: ${{ needs.shared.outputs.short-commit }}
|
||||
# secrets: inherit
|
||||
|
||||
tests-run:
|
||||
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||
needs: [shared, tests-build]
|
||||
uses: ./.github/workflows/sidestore-tests-run.yml
|
||||
with:
|
||||
release_tag: ${{ inputs.release_tag }}
|
||||
short_commit: ${{ needs.shared.outputs.short-commit }}
|
||||
secrets: inherit
|
||||
# tests-run:
|
||||
# if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||
# needs: [shared, tests-build]
|
||||
# uses: ./.github/workflows/sidestore-tests-run.yml
|
||||
# with:
|
||||
# release_tag: ${{ inputs.release_tag }}
|
||||
# short_commit: ${{ needs.shared.outputs.short-commit }}
|
||||
# secrets: inherit
|
||||
|
||||
deploy:
|
||||
needs: [shared, build, tests-build, tests-run] # Keep tests-run in needs
|
||||
# needs: [shared, build, tests-build, tests-run] # Keep tests-run in needs
|
||||
needs: [shared, build] # Keep tests-run in needs
|
||||
if: ${{ always() && (needs.tests-run.result == 'skipped' || needs.tests-run.result == 'success') }}
|
||||
uses: ./.github/workflows/sidestore-deploy.yml
|
||||
with:
|
||||
|
||||
47
.github/workflows/sidestore-build.yml
vendored
@@ -35,8 +35,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-15'
|
||||
version: '16.2'
|
||||
- os: 'macos-26'
|
||||
version: '26.0'
|
||||
runs-on: ${{ matrix.os }}
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
@@ -173,45 +173,6 @@ jobs:
|
||||
# swiftpm-cache-restore-keys: |
|
||||
# xcode-cache-sourcedata-build-${{ github.ref_name }}-
|
||||
|
||||
- name: (Build) Restore Pods from Cache (Exact match)
|
||||
id: pods-restore
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-build-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||
# restore-keys: | # commented out to strictly check cache for this particular podfile
|
||||
# pods-cache-
|
||||
|
||||
- name: (Build) Restore Pods from Cache (Last Available)
|
||||
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||
id: pods-restore-recent
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-build-${{ github.ref_name }}-
|
||||
|
||||
|
||||
- name: (Build) Install CocoaPods
|
||||
run: pod install
|
||||
shell: bash
|
||||
|
||||
- name: (Build) Save Pods to Cache
|
||||
id: save-pods
|
||||
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-build-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||
|
||||
- name: (Build) Clean previous build artifacts
|
||||
# using 'tee' to intercept stdout and log for detailed build-log
|
||||
run: |
|
||||
@@ -227,10 +188,6 @@ jobs:
|
||||
ls -la .
|
||||
echo ""
|
||||
|
||||
echo ">>>>>>>>> Pods <<<<<<<<<<"
|
||||
find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||
echo ""
|
||||
|
||||
echo ">>>>>>>>> SideStore <<<<<<<<<<"
|
||||
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||
echo ""
|
||||
|
||||
92
.github/workflows/sidestore-deploy.yml
vendored
@@ -116,6 +116,49 @@ jobs:
|
||||
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
|
||||
- name: List files to upload
|
||||
id: list_uploads
|
||||
run: |
|
||||
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
||||
find . -maxdepth 4 -exec ls -ld {} + || true # List contents if directory exists
|
||||
echo ""
|
||||
|
||||
FILES="SideStore.ipa SideStore.dSYMs.zip encrypted-build-logs.zip"
|
||||
|
||||
if [[ "${{ vars.ENABLE_TESTS }}" == "1" && "${{ vars.ENABLE_TESTS_BUILD }}" == "1" ]]; then
|
||||
FILES="$FILES encrypted-tests-build-logs.zip"
|
||||
fi
|
||||
|
||||
if [[ "${{ vars.ENABLE_TESTS }}" == "1" && "${{ vars.ENABLE_TESTS_RUN }}" == "1" ]]; then
|
||||
FILES="$FILES encrypted-tests-run-logs.zip test-results.zip tests-recording.mp4"
|
||||
fi
|
||||
|
||||
echo "Final upload list:"
|
||||
for f in $FILES; do
|
||||
if [[ -f "$f" ]]; then
|
||||
echo " ✓ $f"
|
||||
else
|
||||
echo " - $f (missing)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "files=$FILES" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set Upstream Recommendation
|
||||
id: upstream_recommendation
|
||||
run: |
|
||||
UPSTREAM_NAME=$(echo "${{ inputs.upstream_name }}" | tr '[:upper:]' '[:lower:]')
|
||||
if [[ "$UPSTREAM_NAME" != "nightly" ]]; then
|
||||
echo "content<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "If you want to try out new features early but want a lower chance of bugs, you can look at [SideStore ${{ inputs.upstream_name }}](https://github.com/${{ github.repository }}/releases?q=${{ inputs.upstream_tag }})." >> $GITHUB_OUTPUT
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "content=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Upload to releases
|
||||
uses: IsaacShelton/update-existing-release@v1.3.1
|
||||
with:
|
||||
@@ -123,22 +166,21 @@ jobs:
|
||||
release: ${{ inputs.release_name }}
|
||||
tag: ${{ inputs.release_tag }}
|
||||
prerelease: ${{ inputs.is_beta }}
|
||||
files: SideStore.ipa SideStore.dSYMs.zip encrypted-build-logs.zip encrypted-tests-build-logs.zip encrypted-tests-run-logs.zip test-results.zip tests-recording.mp4
|
||||
files: ${{ steps.list_uploads.outputs.files }}
|
||||
body: |
|
||||
This is an ⚠️ **EXPERIMENTAL** ⚠️ ${{ inputs.release_name }} build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
|
||||
This is an ⚠️ **EXPERIMENTAL** ⚠️ ${{ inputs.release_name }} build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
|
||||
|
||||
${{ inputs.release_name }} builds are **extremely experimental builds only meant to be used by developers and beta testers. They often contain bugs and experimental features. Use at your own risk!**
|
||||
${{ inputs.release_name }} builds are **extremely experimental builds only meant to be used by developers and beta testers. They often contain bugs and experimental features. Use at your own risk!**
|
||||
|
||||
If you want to try out new features early but want a lower chance of bugs, you can look at [SideStore ${{ inputs.upstream_name }}](https://github.com/${{ github.repository }}/releases?q=${{ inputs.upstream_tag }}).
|
||||
${{ steps.upstream_recommendation.outputs.content }}
|
||||
## Build Info
|
||||
|
||||
## Build Info
|
||||
Built at (UTC): `${{ steps.date.outputs.date }}`
|
||||
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||
Commit SHA: `${{ github.sha }}`
|
||||
Version: `${{ inputs.version }}`
|
||||
|
||||
Built at (UTC): `${{ steps.date.outputs.date }}`
|
||||
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||
Commit SHA: `${{ github.sha }}`
|
||||
Version: `${{ inputs.version }}`
|
||||
|
||||
${{ steps.release_notes.outputs.content }}
|
||||
${{ steps.release_notes.outputs.content }}
|
||||
|
||||
- name: Get formatted date
|
||||
run: |
|
||||
@@ -169,18 +211,6 @@ jobs:
|
||||
|
||||
- name: Set Release Info variables
|
||||
run: |
|
||||
# Format localized description
|
||||
LOCALIZED_DESCRIPTION=$(cat <<EOF
|
||||
This is release for:
|
||||
- version: "${{ inputs.version }}"
|
||||
- revision: "${{ inputs.short_commit }}"
|
||||
- timestamp: "${{ steps.date.outputs.date }}"
|
||||
|
||||
Release Notes:
|
||||
${{ steps.release_notes.outputs.content }}
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "IS_BETA=${{ inputs.is_beta }}" >> $GITHUB_ENV
|
||||
echo "BUNDLE_IDENTIFIER=${{ inputs.bundle_id }}" >> $GITHUB_ENV
|
||||
echo "VERSION_IPA=${{ inputs.marketing_version }}" >> $GITHUB_ENV
|
||||
@@ -190,6 +220,22 @@ jobs:
|
||||
echo "SHA256=$SHA256_HASH" >> $GITHUB_ENV
|
||||
echo "DOWNLOAD_URL=https://github.com/SideStore/SideStore/releases/download/${{ inputs.release_tag }}/SideStore.ipa" >> $GITHUB_ENV
|
||||
|
||||
# Format localized description
|
||||
get_description() {
|
||||
cat <<EOF
|
||||
This is release for:
|
||||
- version: "${{ inputs.version }}"
|
||||
- revision: "${{ inputs.short_commit }}"
|
||||
- timestamp: "${{ steps.date.outputs.date }}"
|
||||
|
||||
Release Notes:
|
||||
${{ steps.release_notes.outputs.content }}
|
||||
EOF
|
||||
}
|
||||
|
||||
LOCALIZED_DESCRIPTION=$(get_description)
|
||||
echo "$LOCALIZED_DESCRIPTION"
|
||||
|
||||
# multiline strings
|
||||
echo "LOCALIZED_DESCRIPTION<<EOF" >> $GITHUB_ENV
|
||||
echo "$LOCALIZED_DESCRIPTION" >> $GITHUB_ENV
|
||||
|
||||
45
.github/workflows/sidestore-tests-build.yml
vendored
@@ -19,8 +19,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-15'
|
||||
version: '16.2'
|
||||
- os: 'macos-26'
|
||||
version: '26.0'
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
with:
|
||||
xcode-version: '16.2'
|
||||
xcode-version: '26.0'
|
||||
|
||||
# - name: (Tests-Build) Cache Build
|
||||
# uses: irgaly/xcode-cache@v1.8.1
|
||||
@@ -68,41 +68,6 @@ jobs:
|
||||
~/Library/Caches/org.swift.swiftpm
|
||||
key: xcode-cache-tests-${{ github.ref_name }}-
|
||||
|
||||
- name: (Tests-Build) Restore Pods from Cache (Exact match)
|
||||
id: pods-restore
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||
|
||||
- name: (Tests-Build) Restore Pods from Cache (Last Available)
|
||||
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||
id: pods-restore-recent
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-test-${{ github.ref_name }}-
|
||||
|
||||
- name: (Tests-Build) Install CocoaPods
|
||||
run: pod install
|
||||
shell: bash
|
||||
|
||||
- name: (Tests-Build) Save Pods to Cache
|
||||
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||
|
||||
- name: Clean Derived Data (if required)
|
||||
if: ${{ vars.PERFORM_CLEAN_TESTS_BUILD == '1' }}
|
||||
run: |
|
||||
@@ -124,10 +89,6 @@ jobs:
|
||||
ls -la .
|
||||
echo ""
|
||||
|
||||
echo ">>>>>>>>> Pods <<<<<<<<<<"
|
||||
find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||
echo ""
|
||||
|
||||
echo ">>>>>>>>> SideStore <<<<<<<<<<"
|
||||
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||
echo ""
|
||||
|
||||
45
.github/workflows/sidestore-tests-run.yml
vendored
@@ -19,8 +19,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-15'
|
||||
version: '16.2'
|
||||
- os: 'macos-26'
|
||||
version: '26.0'
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
with:
|
||||
xcode-version: '16.2'
|
||||
xcode-version: '26.0'
|
||||
|
||||
# - name: (Tests-Run) Cache Build
|
||||
# uses: irgaly/xcode-cache@v1.8.1
|
||||
@@ -56,41 +56,6 @@ jobs:
|
||||
~/Library/Caches/org.swift.swiftpm
|
||||
key: xcode-cache-tests-${{ github.ref_name }}-${{ github.sha }}
|
||||
|
||||
- name: (Tests-Run) Restore Pods from Cache (Exact match)
|
||||
id: pods-restore
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||
|
||||
- name: (Tests-Run) Restore Pods from Cache (Last Available)
|
||||
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||
id: pods-restore-recent
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-test-${{ github.ref_name }}-
|
||||
|
||||
- name: (Tests-Run) Install CocoaPods
|
||||
run: pod install
|
||||
shell: bash
|
||||
|
||||
- name: (Tests-Run) Save Pods to Cache
|
||||
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||
|
||||
- name: (Tests-Run) Clean previous build artifacts
|
||||
run: |
|
||||
make clean
|
||||
@@ -104,10 +69,6 @@ jobs:
|
||||
ls -la .
|
||||
echo ""
|
||||
|
||||
echo ">>>>>>>>> Pods <<<<<<<<<<"
|
||||
find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||
echo ""
|
||||
|
||||
echo ">>>>>>>>> SideStore <<<<<<<<<<"
|
||||
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||
echo ""
|
||||
|
||||
45
.github/workflows/stable.yml
vendored
@@ -12,8 +12,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-15'
|
||||
version: '16.2'
|
||||
- os: 'macos-26'
|
||||
version: '26.0'
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
@@ -89,43 +89,6 @@ jobs:
|
||||
~/Library/Caches/org.swift.swiftpm
|
||||
key: xcode-cache-build-stable-
|
||||
|
||||
- name: (Build) Restore Pods from Cache (Exact match)
|
||||
id: pods-restore
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-build-stable-${{ hashFiles('Podfile') }}
|
||||
|
||||
- name: (Build) Restore Pods from Cache (Last Available)
|
||||
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||
id: pods-restore-recent
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-build-stable-
|
||||
|
||||
|
||||
- name: (Build) Install CocoaPods
|
||||
run: pod install
|
||||
shell: bash
|
||||
|
||||
- name: (Build) Save Pods to Cache
|
||||
id: save-pods
|
||||
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: |
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-build-stable-${{ hashFiles('Podfile') }}
|
||||
|
||||
- name: (Build) Clean previous build artifacts
|
||||
run: |
|
||||
make clean
|
||||
@@ -140,10 +103,6 @@ jobs:
|
||||
ls -la .
|
||||
echo ""
|
||||
|
||||
echo ">>>>>>>>> Pods <<<<<<<<<<"
|
||||
find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||
echo ""
|
||||
|
||||
echo ">>>>>>>>> SideStore <<<<<<<<<<"
|
||||
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||
echo ""
|
||||
|
||||
6
.gitmodules
vendored
@@ -30,7 +30,7 @@
|
||||
url = https://github.com/rileytestut/Roxas.git
|
||||
[submodule "Dependencies/libimobiledevice"]
|
||||
path = Dependencies/libimobiledevice
|
||||
url = https://github.com/libimobiledevice/libimobiledevice
|
||||
url = https://github.com/SideStore/libimobiledevice
|
||||
[submodule "Dependencies/libusbmuxd"]
|
||||
path = Dependencies/libusbmuxd
|
||||
url = https://github.com/libimobiledevice/libusbmuxd.git
|
||||
@@ -51,8 +51,8 @@
|
||||
url = https://github.com/SideStore/minimuxer
|
||||
branch = master
|
||||
[submodule "SideStore/em_proxy"]
|
||||
path = SideStore/em_proxy
|
||||
url = https://github.com/SideStore/em_proxy
|
||||
path = SideStore/em_proxy
|
||||
url = https://github.com/SideStore/em_proxy
|
||||
branch = master
|
||||
[submodule "SideStore/libfragmentzip"]
|
||||
path = SideStore/libfragmentzip
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||
<string>group.$(GROUP_ID)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
3
AltStore.xcworkspace/contents.xcworkspacedata
generated
@@ -10,7 +10,4 @@
|
||||
<FileRef
|
||||
location = "group:Dependencies/Roxas/Roxas.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
@@ -2,24 +2,14 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- <key>com.apple.security.files.user-selected.read-write</key>
|
||||
<array>
|
||||
<string></string>
|
||||
</array>
|
||||
<key>com.apple.developer.applesignin</key>
|
||||
<array>
|
||||
<string></string>
|
||||
</array> -->
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.kernel.increased-debugging-memory-limit</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.kernel.increased-memory-limit</key>
|
||||
<true/>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.siri</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||
|
||||
10
AltStore/AltStoreFree.entitlements
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,115 +0,0 @@
|
||||
//
|
||||
// AnalyticsManager.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 3/31/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
import AppCenter
|
||||
import AppCenterAnalytics
|
||||
import AppCenterCrashes
|
||||
|
||||
#if DEBUG
|
||||
private let appCenterAppSecret = "73532d3e-e573-4693-99a4-9f85840bbb44"
|
||||
#elseif RELEASE
|
||||
private let appCenterAppSecret = "73532d3e-e573-4693-99a4-9f85840bbb44"
|
||||
#else
|
||||
private let appCenterAppSecret = "73532d3e-e573-4693-99a4-9f85840bbb44"
|
||||
#endif
|
||||
|
||||
extension AnalyticsManager
|
||||
{
|
||||
enum EventProperty: String
|
||||
{
|
||||
case name
|
||||
case bundleIdentifier
|
||||
case developerName
|
||||
case version
|
||||
case buildVersion
|
||||
case size
|
||||
case tintColor
|
||||
case sourceIdentifier
|
||||
case sourceURL
|
||||
case patreonURL
|
||||
case pledgeAmount
|
||||
case pledgeCurrency
|
||||
}
|
||||
|
||||
enum Event
|
||||
{
|
||||
case installedApp(InstalledApp)
|
||||
case updatedApp(InstalledApp)
|
||||
case refreshedApp(InstalledApp)
|
||||
|
||||
var name: String {
|
||||
switch self
|
||||
{
|
||||
case .installedApp: return "installed_app"
|
||||
case .updatedApp: return "updated_app"
|
||||
case .refreshedApp: return "refreshed_app"
|
||||
}
|
||||
}
|
||||
|
||||
var properties: [EventProperty: String] {
|
||||
let properties: [EventProperty: String?]
|
||||
|
||||
switch self
|
||||
{
|
||||
case .installedApp(let app), .updatedApp(let app), .refreshedApp(let app):
|
||||
let appBundleURL = InstalledApp.fileURL(for: app)
|
||||
let appBundleSize = FileManager.default.directorySize(at: appBundleURL)
|
||||
|
||||
properties = [
|
||||
.name: app.name,
|
||||
.bundleIdentifier: app.bundleIdentifier,
|
||||
.developerName: app.storeApp?.developerName,
|
||||
.version: app.version,
|
||||
.buildVersion: app.buildVersion,
|
||||
.size: appBundleSize?.description,
|
||||
.tintColor: app.storeApp?.tintColor?.hexString,
|
||||
.sourceIdentifier: app.storeApp?.sourceIdentifier,
|
||||
.sourceURL: app.storeApp?.source?.sourceURL.absoluteString,
|
||||
.patreonURL: app.storeApp?.source?.patreonURL?.absoluteString,
|
||||
.pledgeAmount: app.storeApp?.pledgeAmount?.description,
|
||||
.pledgeCurrency: app.storeApp?.pledgeCurrency
|
||||
]
|
||||
}
|
||||
|
||||
return properties.compactMapValues { $0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class AnalyticsManager
|
||||
{
|
||||
static let shared = AnalyticsManager()
|
||||
|
||||
private init()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
extension AnalyticsManager
|
||||
{
|
||||
func start()
|
||||
{
|
||||
AppCenter.start(withAppSecret: appCenterAppSecret, services: [
|
||||
Analytics.self,
|
||||
Crashes.self
|
||||
])
|
||||
}
|
||||
|
||||
func trackEvent(_ event: Event)
|
||||
{
|
||||
let properties = event.properties.reduce(into: [:]) { (properties, item) in
|
||||
properties[item.key.rawValue] = item.value
|
||||
}
|
||||
|
||||
Analytics.trackEvent(event.name, withProperties: properties)
|
||||
}
|
||||
}
|
||||
@@ -27,10 +27,12 @@ extension AppDelegate
|
||||
static let addSourceDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".AddSourceDeepLinkNotification")
|
||||
|
||||
static let appBackupDidFinish = Notification.Name(Bundle.Info.appbundleIdentifier + ".AppBackupDidFinish")
|
||||
static let exportCertificateNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".ExportCertificateNotification")
|
||||
|
||||
static let importAppDeepLinkURLKey = "fileURL"
|
||||
static let appBackupResultKey = "result"
|
||||
static let addSourceDeepLinkURLKey = "sourceURL"
|
||||
static let exportCertificateCallbackTemplateKey = "callback"
|
||||
}
|
||||
|
||||
@UIApplicationMain
|
||||
@@ -91,13 +93,14 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
AnalyticsManager.shared.start()
|
||||
|
||||
self.setTintColor()
|
||||
self.setTintColor()
|
||||
self.prepareImageCache()
|
||||
|
||||
// TODO: @mahee96: find if we need to start em_proxy as in altstore?
|
||||
// start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
if UserDefaults.standard.enableEMPforWireguard {
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
}
|
||||
|
||||
SecureValueTransformer.register()
|
||||
|
||||
@@ -122,7 +125,9 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
{
|
||||
// Make sure to update SceneDelegate.sceneDidEnterBackground() as well.
|
||||
// TODO: @mahee96: find if we need to stop em_proxy as in altstore?
|
||||
// stop_em_proxy()
|
||||
if UserDefaults.standard.enableEMPforWireguard {
|
||||
stop_em_proxy()
|
||||
}
|
||||
guard let oneMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date()) else { return }
|
||||
|
||||
let midnightOneMonthAgo = Calendar.current.startOfDay(for: oneMonthAgo)
|
||||
@@ -139,9 +144,9 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
func applicationWillEnterForeground(_ application: UIApplication)
|
||||
{
|
||||
AppManager.shared.update()
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
|
||||
PatreonAPI.shared.refreshPatreonAccount()
|
||||
if UserDefaults.standard.enableEMPforWireguard {
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
}
|
||||
}
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
|
||||
@@ -292,6 +297,26 @@ private extension AppDelegate
|
||||
|
||||
return true
|
||||
|
||||
case "pairing":
|
||||
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
|
||||
guard let callbackTemplate = queryItems["urlName"]?.removingPercentEncoding else { return false }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
exportPairingFile(callbackTemplate)
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
case "certificate":
|
||||
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
|
||||
guard let callbackTemplate = queryItems["callback_template"]?.removingPercentEncoding else { return false }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: AppDelegate.exportCertificateNotification, object: nil, userInfo: [AppDelegate.exportCertificateCallbackTemplateKey: callbackTemplate])
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
@@ -14,7 +14,7 @@
|
||||
<objects>
|
||||
<navigationController storyboardIdentifier="navigationController" id="ZTo-53-dSL" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Aej-RF-PfV" customClass="NavigationBar" customModule="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"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="barTintColor" name="SettingsBackground"/>
|
||||
@@ -42,13 +42,13 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<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>
|
||||
<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"/>
|
||||
<subviews>
|
||||
<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>
|
||||
<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"/>
|
||||
@@ -57,13 +57,13 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="67.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Welcome to 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="333.5" height="41"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="41"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Sign in with your Apple ID to get started." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="SNU-tv-8Au">
|
||||
<rect key="frame" x="0.0" y="47" width="308.5" height="20.5"/>
|
||||
<rect key="frame" x="0.0" y="47" width="306.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -160,7 +160,7 @@
|
||||
</stackView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2N5-zd-fUj">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2N5-zd-fUj">
|
||||
<rect key="frame" x="0.0" y="191" width="343" height="51"/>
|
||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||
<constraints>
|
||||
@@ -179,7 +179,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<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>
|
||||
<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"/>
|
||||
@@ -198,6 +198,10 @@
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="DBk-rT-ZE8" firstAttribute="leading" secondItem="2wp-qG-f0Z" secondAttribute="leadingMargin" id="5AT-nV-ZP9"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="DBk-rT-ZE8" secondAttribute="bottom" id="HgY-oY-8KM"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="DBk-rT-ZE8" secondAttribute="trailing" id="VCf-bW-2K4"/>
|
||||
<constraint firstItem="YmX-7v-pxh" firstAttribute="top" secondItem="2wp-qG-f0Z" secondAttribute="top" constant="6" id="iUr-Nd-tkt"/>
|
||||
<constraint firstItem="DBk-rT-ZE8" firstAttribute="top" relation="greaterThanOrEqual" secondItem="YmX-7v-pxh" secondAttribute="bottom" constant="8" symbolic="YES" id="zTU-eY-DWd"/>
|
||||
</constraints>
|
||||
</view>
|
||||
@@ -215,19 +219,15 @@
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="WXx-hX-AXv" secondAttribute="bottom" id="0jL-Ky-ju6"/>
|
||||
<constraint firstAttribute="leadingMargin" secondItem="YmX-7v-pxh" secondAttribute="leading" id="2PO-lG-dmB"/>
|
||||
<constraint firstItem="DBk-rT-ZE8" firstAttribute="leading" secondItem="2wp-qG-f0Z" secondAttribute="leadingMargin" id="5AT-nV-ZP9"/>
|
||||
<constraint firstItem="oyW-Fd-ojD" firstAttribute="top" secondItem="zMn-DV-fpy" secondAttribute="top" id="730-db-ukB"/>
|
||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="bottomMargin" secondItem="DBk-rT-ZE8" secondAttribute="bottom" id="HgY-oY-8KM"/>
|
||||
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="oyW-Fd-ojD" secondAttribute="trailing" id="KGE-CN-SWf"/>
|
||||
<constraint firstItem="WXx-hX-AXv" firstAttribute="top" secondItem="mjy-4S-hyH" secondAttribute="top" id="LPQ-bF-ic0"/>
|
||||
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="WXx-hX-AXv" secondAttribute="trailing" id="MG7-A6-pKp"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="YmX-7v-pxh" secondAttribute="trailing" id="O4T-nu-o3e"/>
|
||||
<constraint firstItem="zMn-DV-fpy" firstAttribute="bottom" secondItem="oyW-Fd-ojD" secondAttribute="bottom" id="PuX-ab-cEq"/>
|
||||
<constraint firstItem="oyW-Fd-ojD" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="SzC-gC-Nvi"/>
|
||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="trailingMargin" secondItem="DBk-rT-ZE8" secondAttribute="trailing" id="VCf-bW-2K4"/>
|
||||
<constraint firstItem="WXx-hX-AXv" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="d08-zF-5X6"/>
|
||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="height" secondItem="oyW-Fd-ojD" secondAttribute="height" id="dFN-pw-TWt"/>
|
||||
<constraint firstItem="YmX-7v-pxh" firstAttribute="top" secondItem="2wp-qG-f0Z" secondAttribute="top" constant="6" id="iUr-Nd-tkt"/>
|
||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="width" secondItem="oyW-Fd-ojD" secondAttribute="width" id="rYO-GN-0Lk"/>
|
||||
</constraints>
|
||||
</view>
|
||||
@@ -264,7 +264,7 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<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>
|
||||
<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"/>
|
||||
@@ -298,7 +298,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<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>
|
||||
<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"/>
|
||||
@@ -310,7 +310,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="dMu-eg-gIO">
|
||||
<rect key="frame" x="79" y="15.5" width="264" height="64"/>
|
||||
<rect key="frame" x="79" y="16" width="264" height="64"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Connect to 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"/>
|
||||
@@ -318,7 +318,7 @@
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="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 LocalDevVPN and use Sidestore on the go." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="4rk-ge-FSj">
|
||||
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -329,7 +329,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<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>
|
||||
<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"/>
|
||||
@@ -341,7 +341,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<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>
|
||||
<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"/>
|
||||
@@ -360,7 +360,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<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>
|
||||
<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"/>
|
||||
@@ -372,7 +372,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Xs6-pJ-PUz">
|
||||
<rect key="frame" x="79" y="16" width="264" height="64"/>
|
||||
<rect key="frame" x="79" y="17" width="264" height="62"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apps Refresh Automatically" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="nvb-Aq-sYa">
|
||||
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
||||
@@ -381,7 +381,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apps are refreshed in the background while you are on SideStore VPN!" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="HU5-Hv-E3d">
|
||||
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
||||
<rect key="frame" x="0.0" y="25.5" width="264" height="36.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -431,7 +431,7 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1353" y="736"/>
|
||||
</scene>
|
||||
<!--Refresh AltStore-->
|
||||
<!--Refresh SideStore-->
|
||||
<scene sceneID="9Vh-dM-OqX">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="refreshAltStoreViewController" id="aoK-yE-UVT" customClass="RefreshAltStoreViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||
@@ -485,7 +485,7 @@
|
||||
<constraint firstItem="tDQ-ao-1Jg" firstAttribute="leading" secondItem="R83-kV-365" secondAttribute="leadingMargin" id="zEt-Xr-kJx"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Refresh AltStore" largeTitleDisplayMode="always" id="5nk-NR-jtV"/>
|
||||
<navigationItem key="navigationItem" title="Refresh SideStore" largeTitleDisplayMode="always" id="5nk-NR-jtV"/>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||
<connections>
|
||||
<outlet property="placeholderView" destination="fpO-Bf-gFY" id="q7d-au-d94"/>
|
||||
|
||||
@@ -532,6 +532,7 @@
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="tintColor" name="Primary"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
@@ -561,6 +562,7 @@
|
||||
<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="20" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
@@ -913,6 +915,7 @@
|
||||
<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="108"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
|
||||
@@ -12,84 +12,73 @@ import EmotionalDamage
|
||||
import minimuxer
|
||||
import WidgetKit
|
||||
|
||||
import AltSign
|
||||
import AltStoreCore
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
let pairingFileName = "ALTPairingFile.mobiledevicepairing"
|
||||
|
||||
final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
|
||||
{
|
||||
final class LaunchViewController: UIViewController, UIDocumentPickerDelegate {
|
||||
private var didFinishLaunching = false
|
||||
|
||||
private var destinationViewController: TabBarController!
|
||||
|
||||
override var launchConditions: [RSTLaunchCondition] {
|
||||
let isDatabaseStarted = RSTLaunchCondition(condition: { DatabaseManager.shared.isStarted }) { (completionHandler) in
|
||||
DatabaseManager.shared.start(completionHandler: completionHandler)
|
||||
}
|
||||
private var retries = 0
|
||||
private var maxRetries = 3
|
||||
private var splashView: SplashView!
|
||||
private var destinationViewController: TabBarController?
|
||||
private var startTime: Date!
|
||||
|
||||
return [isDatabaseStarted]
|
||||
}
|
||||
|
||||
override var childForStatusBarStyle: UIViewController? {
|
||||
return self.children.first
|
||||
}
|
||||
|
||||
override var childForStatusBarHidden: UIViewController? {
|
||||
return self.children.first
|
||||
}
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
defer {
|
||||
// Create destinationViewController now so view controllers can register for receiving Notifications.
|
||||
self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController
|
||||
}
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
splashView = SplashView(frame: view.bounds, appName: "SideStore")
|
||||
destinationViewController = storyboard!.instantiateViewController(withIdentifier: "tabBarController") as? TabBarController
|
||||
view.addSubview(splashView)
|
||||
}
|
||||
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
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")
|
||||
super.viewDidAppear(animated)
|
||||
guard !didFinishLaunching else { return }
|
||||
Task {
|
||||
startTime = Date()
|
||||
await runLaunchSequence()
|
||||
doPostLaunch()
|
||||
}
|
||||
}
|
||||
|
||||
private func runLaunchSequence() async {
|
||||
guard retries < maxRetries else { return }
|
||||
retries += 1
|
||||
|
||||
await Task.detached {
|
||||
if !DatabaseManager.shared.isStarted {
|
||||
await withCheckedContinuation { continuation in
|
||||
DatabaseManager.shared.start { error in
|
||||
if let error {
|
||||
Task { await self.handleLaunchError(error, retryCallback: self.runLaunchSequence) }
|
||||
} else {
|
||||
Task { await self.finishLaunching() }
|
||||
}
|
||||
continuation.resume(returning: ())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await self.finishLaunching()
|
||||
}
|
||||
}
|
||||
|
||||
}.value
|
||||
}
|
||||
|
||||
private func doPostLaunch() {
|
||||
SideJITManager.shared.checkAndPromptIfNeeded(presentingVC: self)
|
||||
if #available(iOS 17, *), UserDefaults.standard.sidejitenable {
|
||||
DispatchQueue.global().async {
|
||||
self.askfornetwork()
|
||||
}
|
||||
DispatchQueue.global().async { SideJITManager.shared.askForNetwork() }
|
||||
print("SideJITServer Enabled")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
|
||||
detectAndImportAccountFile()
|
||||
|
||||
if UserDefaults.standard.enableEMPforWireguard {
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
}
|
||||
guard let pf = fetchPairingFile() else {
|
||||
displayError("Device pairing file not found.")
|
||||
return
|
||||
@@ -97,222 +86,124 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
start_minimuxer_threads(pf)
|
||||
#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? {
|
||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||
let fm = FileManager.default
|
||||
let documentsPath = fm.documentsDirectory.appendingPathComponent("/\(filename)")
|
||||
if fm.fileExists(atPath: documentsPath.path), let contents = try? String(contentsOf: documentsPath), !contents.isEmpty {
|
||||
print("Loaded ALTPairingFile from \(documentsPath.path)")
|
||||
return contents
|
||||
} else if
|
||||
let appResourcePath = Bundle.main.url(forResource: "ALTPairingFile", withExtension: "mobiledevicepairing"),
|
||||
fm.fileExists(atPath: appResourcePath.path),
|
||||
let data = fm.contents(atPath: appResourcePath.path),
|
||||
let contents = String(data: data, encoding: .utf8),
|
||||
!contents.isEmpty,
|
||||
!UserDefaults.standard.isPairingReset {
|
||||
print("Loaded ALTPairingFile from \(appResourcePath.path)")
|
||||
return contents
|
||||
} 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")
|
||||
return plistString
|
||||
} else {
|
||||
// Show an alert explaining the pairing file
|
||||
// Create new 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
|
||||
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
||||
// Try to load it from a file picker
|
||||
var types = UTType.types(tag: "plist", tagClass: UTTagClass.filenameExtension, conformingTo: nil)
|
||||
types.append(contentsOf: UTType.types(tag: "mobiledevicepairing", tagClass: UTTagClass.filenameExtension, conformingTo: UTType.data))
|
||||
types.append(.xml)
|
||||
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: types)
|
||||
documentPickerController.shouldShowFileExtensions = true
|
||||
documentPickerController.delegate = self
|
||||
self.present(documentPickerController, animated: true, completion: nil)
|
||||
UserDefaults.standard.isPairingReset = false
|
||||
})
|
||||
|
||||
//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)
|
||||
|
||||
// Present Alert to
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func displayError(_ msg: String) {
|
||||
print(msg)
|
||||
// Create a new alert
|
||||
let dialogMessage = UIAlertController(title: "Error launching SideStore", message: msg, preferredStyle: .alert)
|
||||
|
||||
// Present alert to user
|
||||
self.present(dialogMessage, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
||||
let url = urls[0]
|
||||
let isSecuredURL = url.startAccessingSecurityScopedResource() == true
|
||||
|
||||
do {
|
||||
// Read to a string
|
||||
let data1 = try Data(contentsOf: urls[0])
|
||||
let pairing_string = String(bytes: data1, encoding: .utf8)
|
||||
if pairing_string == nil {
|
||||
displayError("Unable to read pairing file")
|
||||
}
|
||||
|
||||
// Save to a file for next launch
|
||||
let pairingFile = FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)")
|
||||
try pairing_string?.write(to: pairingFile, atomically: true, encoding: String.Encoding.utf8)
|
||||
|
||||
// Start minimuxer now that we have a file
|
||||
start_minimuxer_threads(pairing_string!)
|
||||
} catch {
|
||||
displayError("Unable to read pairing file")
|
||||
}
|
||||
|
||||
if (isSecuredURL) {
|
||||
url.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
controller.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
|
||||
displayError("Choosing a pairing file was cancelled. Please re-open the app and try again.")
|
||||
}
|
||||
|
||||
func start_minimuxer_threads(_ pairing_file: String) {
|
||||
target_minimuxer_address()
|
||||
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
|
||||
do {
|
||||
// enable minimuxer console logging only if enabled in settings
|
||||
let isMinimuxerConsoleLoggingEnabled = UserDefaults.standard.isMinimuxerConsoleLoggingEnabled
|
||||
try minimuxer.startWithLogger(pairing_file, documentsDirectory, isMinimuxerConsoleLoggingEnabled)
|
||||
let loggingEnabled = UserDefaults.standard.isMinimuxerConsoleLoggingEnabled
|
||||
try minimuxer.startWithLogger(pairing_file, documentsDirectory, loggingEnabled)
|
||||
} catch {
|
||||
try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)"))
|
||||
displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR!!!!!! REPORT TO GITHUB ISSUES!")")
|
||||
try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent(pairingFileName))
|
||||
displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR")")
|
||||
}
|
||||
if #available(iOS 17, *) {
|
||||
// TODO: iOS 17 and above have a new JIT implementation that is completely broken in SideStore :(
|
||||
start_auto_mounter(documentsDirectory)
|
||||
}
|
||||
|
||||
func fetchPairingFile() -> String? { PairingFileManager.shared.fetchPairingFile(presentingVC: self) }
|
||||
|
||||
func displayError(_ msg: String) {
|
||||
print(msg)
|
||||
let alert = UIAlertController(title: "Error launching SideStore", message: msg, preferredStyle: .alert)
|
||||
self.present(alert, animated: true)
|
||||
}
|
||||
|
||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
||||
let url = urls[0]
|
||||
let isSecuredURL = url.startAccessingSecurityScopedResource() == true
|
||||
defer {
|
||||
if (isSecuredURL) {
|
||||
url.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
}
|
||||
else {
|
||||
start_auto_mounter(documentsDirectory)
|
||||
|
||||
do {
|
||||
let data = try Data(contentsOf: url)
|
||||
guard let pairingString = String(data: data, encoding: .utf8) else {
|
||||
displayError("Unable to read pairing file")
|
||||
return
|
||||
}
|
||||
try pairingString.write(to: FileManager.default.documentsDirectory.appendingPathComponent(pairingFileName), atomically: true, encoding: .utf8)
|
||||
start_minimuxer_threads(pairingString)
|
||||
} catch {
|
||||
displayError("Unable to read pairing file")
|
||||
}
|
||||
|
||||
// Create destinationViewController now so view controllers can register for receiving Notifications.
|
||||
self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as? TabBarController
|
||||
controller.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension LaunchViewController
|
||||
{
|
||||
override func handleLaunchError(_ error: Error)
|
||||
{
|
||||
do
|
||||
{
|
||||
throw error
|
||||
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
|
||||
displayError("Choosing a pairing file was cancelled. Please re-open the app and try again.")
|
||||
}
|
||||
|
||||
func importAccountAtFile(_ file: URL, remove: Bool = false) {
|
||||
_ = file.startAccessingSecurityScopedResource()
|
||||
defer { file.stopAccessingSecurityScopedResource() }
|
||||
guard let accountD = try? Data(contentsOf: file) else {
|
||||
return Logger.main.notice("Could not parse data from file \(file)")
|
||||
}
|
||||
catch let error as NSError
|
||||
{
|
||||
let title = error.userInfo[NSLocalizedFailureErrorKey] as? String ?? NSLocalizedString("Unable to Launch SideStore", comment: "")
|
||||
|
||||
let errorDescription: String
|
||||
|
||||
if #available(iOS 14.5, *)
|
||||
{
|
||||
let errorMessages = [error.debugDescription] + error.underlyingErrors.map { ($0 as NSError).debugDescription }
|
||||
errorDescription = errorMessages.joined(separator: "\n\n")
|
||||
}
|
||||
else
|
||||
{
|
||||
errorDescription = error.debugDescription
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: title, message: errorDescription, preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Retry", comment: ""), style: .default, handler: { (action) in
|
||||
self.handleLaunchConditions()
|
||||
}))
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
guard let account = try? Foundation.JSONDecoder().decode(ImportedAccount.self, from: accountD) else {
|
||||
return Logger.main.notice("Could not parse data from file \(file)")
|
||||
}
|
||||
print("We want to import this account probably: \(account)")
|
||||
if remove {
|
||||
try? FileManager.default.removeItem(at: file)
|
||||
}
|
||||
Keychain.shared.appleIDEmailAddress = account.email
|
||||
Keychain.shared.appleIDPassword = account.password
|
||||
Keychain.shared.adiPb = account.adiPB
|
||||
Keychain.shared.identifier = account.local_user
|
||||
if let altCert = ALTCertificate(p12Data: account.cert, password: account.certpass) {
|
||||
Keychain.shared.signingCertificate = altCert.encryptedP12Data(withPassword: "")!
|
||||
Keychain.shared.signingCertificatePassword = account.certpass
|
||||
let toastView = ToastView(text: NSLocalizedString("Successfully imported '\(account.email)'!", comment: ""), detailText: "SideStore should be fully operational!")
|
||||
return toastView.show(in: self)
|
||||
} else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to import account certificate!", comment: ""), detailText: "Failed to create ALTCertificate. Check if the password is correct. Still imported account/adi.pb details!")
|
||||
return toastView.show(in: self)
|
||||
}
|
||||
}
|
||||
|
||||
override func finishLaunching()
|
||||
{
|
||||
super.finishLaunching()
|
||||
|
||||
guard !self.didFinishLaunching else { return }
|
||||
func detectAndImportAccountFile() {
|
||||
let accountFileURL = FileManager.default.documentsDirectory.appendingPathComponent("Account.sideconf")
|
||||
#if !DEBUG
|
||||
importAccountAtFile(accountFileURL, remove: true)
|
||||
#else
|
||||
importAccountAtFile(accountFileURL)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
extension LaunchViewController {
|
||||
@MainActor
|
||||
func handleLaunchError(_ error: Error, retryCallback: (() async -> Void)? = nil) {
|
||||
do { throw error } catch let error as NSError {
|
||||
let title = error.userInfo[NSLocalizedFailureErrorKey] as? String ?? NSLocalizedString("Unable to Launch SideStore", comment: "")
|
||||
let desc: String
|
||||
if #available(iOS 14.5, *) {
|
||||
desc = ([error.debugDescription] + error.underlyingErrors.map { ($0 as NSError).debugDescription }).joined(separator: "\n\n")
|
||||
} else {
|
||||
desc = error.debugDescription
|
||||
}
|
||||
let alert = UIAlertController(title: title, message: desc, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("Retry", comment: ""), style: .default) { _ in
|
||||
Task { await retryCallback?() }
|
||||
})
|
||||
present(alert, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func finishLaunching() async {
|
||||
guard !didFinishLaunching else { return }
|
||||
didFinishLaunching = true
|
||||
|
||||
AppManager.shared.update()
|
||||
AppManager.shared.updatePatronsIfNeeded()
|
||||
PatreonAPI.shared.refreshPatreonAccount()
|
||||
|
||||
AppManager.shared.updateAllSources { result in
|
||||
guard case .failure(let error) = result else { return }
|
||||
Logger.main.error("Failed to update sources on launch. \(error.localizedDescription, privacy: .public)")
|
||||
|
||||
|
||||
let errorDesc = ErrorProcessing(.fullError).getDescription(error: error as NSError)
|
||||
print("Failed to update sources on launch. \(errorDesc)")
|
||||
|
||||
@@ -320,63 +211,64 @@ extension LaunchViewController
|
||||
if String(describing: error).contains("The Internet connection appears to be offline"){
|
||||
mode = .localizedDescription // dont make noise!
|
||||
}
|
||||
|
||||
let toastView = ToastView(error: error, mode: mode)
|
||||
toastView.addTarget(self.destinationViewController, action: #selector(TabBarController.presentSources), for: .touchUpInside)
|
||||
toastView.show(in: self.destinationViewController.selectedViewController ?? self.destinationViewController)
|
||||
toastView.show(in: self.destinationViewController!.selectedViewController ?? self.destinationViewController!)
|
||||
}
|
||||
|
||||
self.updateKnownSources()
|
||||
|
||||
// Ask widgets to be refreshed
|
||||
updateKnownSources()
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
didFinishLaunching = true
|
||||
|
||||
// Add view controller as child (rather than presenting modally)
|
||||
// so tint adjustment + card presentations works correctly.
|
||||
self.destinationViewController.view.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height)
|
||||
self.destinationViewController.view.alpha = 0.0
|
||||
self.addChild(self.destinationViewController)
|
||||
self.view.addSubview(self.destinationViewController.view, pinningEdgesWith: .zero)
|
||||
self.destinationViewController.didMove(toParent: self)
|
||||
let destinationVC = destinationViewController!
|
||||
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.destinationViewController.view.alpha = 1.0
|
||||
}
|
||||
let elapsed = abs(startTime.timeIntervalSinceNow)
|
||||
let remaining = elapsed >= 1 ? 0 : 1 - elapsed
|
||||
try? await Task.sleep(nanoseconds: UInt64(remaining * 1_000_000_000))
|
||||
|
||||
self.didFinishLaunching = true
|
||||
}
|
||||
}
|
||||
destinationVC.loadViewIfNeeded()
|
||||
addChild(destinationVC)
|
||||
destinationVC.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(destinationVC.view)
|
||||
destinationVC.didMove(toParent: self)
|
||||
|
||||
// Pin edges BEFORE animation
|
||||
NSLayoutConstraint.activate([
|
||||
destinationVC.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
destinationVC.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
destinationVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
destinationVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)
|
||||
])
|
||||
|
||||
private extension LaunchViewController
|
||||
{
|
||||
func updateKnownSources()
|
||||
{
|
||||
// Set initial alpha for fade-in
|
||||
destinationVC.view.alpha = 0
|
||||
|
||||
UIView.transition(with: view, duration: 0.3, options: .transitionCrossDissolve) { [self] in
|
||||
self.splashView.alpha = 0
|
||||
destinationVC.view.alpha = 1
|
||||
} completion: { _ in
|
||||
self.splashView.removeFromSuperview()
|
||||
self.destinationViewController = destinationVC
|
||||
}
|
||||
}
|
||||
|
||||
func updateKnownSources() {
|
||||
AppManager.shared.updateKnownSources { result in
|
||||
switch result
|
||||
{
|
||||
switch result {
|
||||
case .failure(let error): print("[ALTLog] Failed to update known sources:", error)
|
||||
case .success((_, let blockedSources)):
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||
let blockedSourceIDs = Set(blockedSources.lazy.map { $0.identifier })
|
||||
let blockedSourceURLs = Set(blockedSources.lazy.compactMap { $0.sourceURL })
|
||||
|
||||
let predicate = NSPredicate(format: "%K IN %@ OR %K IN %@",
|
||||
#keyPath(Source.identifier), blockedSourceIDs,
|
||||
#keyPath(Source.sourceURL), blockedSourceURLs)
|
||||
|
||||
let sourceErrors = Source.all(satisfying: predicate, in: context).map { (source) in
|
||||
let blockedSource = blockedSources.first { $0.identifier == source.identifier }
|
||||
return SourceError.blocked(source, bundleIDs: blockedSource?.bundleIDs, existingSource: source)
|
||||
let predicate = NSPredicate(format: "%K IN %@ OR %K IN %@", #keyPath(Source.identifier), blockedSourceIDs, #keyPath(Source.sourceURL), blockedSourceURLs)
|
||||
let sourceErrors = Source.all(satisfying: predicate, in: context).map { source in
|
||||
let blocked = blockedSources.first { $0.identifier == source.identifier }
|
||||
return SourceError.blocked(source, bundleIDs: blocked?.bundleIDs, existingSource: source)
|
||||
}
|
||||
|
||||
guard !sourceErrors.isEmpty else { return }
|
||||
|
||||
Task {
|
||||
for error in sourceErrors
|
||||
{
|
||||
for error in sourceErrors {
|
||||
let title = String(format: NSLocalizedString("“%@” Blocked", comment: ""), error.$source.name)
|
||||
let message = [error.localizedDescription, error.recoverySuggestion].compactMap { $0 }.joined(separator: "\n\n")
|
||||
|
||||
await self.presentAlert(title: title, message: message)
|
||||
}
|
||||
}
|
||||
@@ -385,3 +277,142 @@ private extension LaunchViewController
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SplashView
|
||||
final class SplashView: UIView {
|
||||
let iconView = UIImageView()
|
||||
let titleLabel = UILabel()
|
||||
|
||||
init(frame: CGRect, appName: String) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .systemBackground
|
||||
setupIcon()
|
||||
setupTitle(appName: appName)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError() }
|
||||
|
||||
private func setupIcon() {
|
||||
let container = UIView()
|
||||
container.translatesAutoresizingMaskIntoConstraints = false
|
||||
container.layer.shadowColor = UIColor.black.cgColor
|
||||
container.layer.shadowOpacity = 0.25
|
||||
container.layer.shadowOffset = CGSize(width: 0, height: 4)
|
||||
container.layer.shadowRadius = 8
|
||||
addSubview(container)
|
||||
|
||||
iconView.image = UIImage(named: "AppIcon") ?? UIImage(named: "AppIcon60x60") ?? UIImage(systemName: "app.fill")
|
||||
iconView.contentMode = .scaleAspectFit
|
||||
iconView.translatesAutoresizingMaskIntoConstraints = false
|
||||
iconView.layer.cornerRadius = 24
|
||||
iconView.clipsToBounds = true
|
||||
container.addSubview(iconView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
container.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
container.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -20),
|
||||
container.widthAnchor.constraint(equalToConstant: 120),
|
||||
container.heightAnchor.constraint(equalToConstant: 120),
|
||||
iconView.topAnchor.constraint(equalTo: container.topAnchor),
|
||||
iconView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
||||
iconView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
|
||||
iconView.trailingAnchor.constraint(equalTo: container.trailingAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
private func setupTitle(appName: String) {
|
||||
titleLabel.text = appName
|
||||
titleLabel.font = .systemFont(ofSize: 24, weight: .bold)
|
||||
titleLabel.textColor = .label
|
||||
titleLabel.textAlignment = .center
|
||||
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(titleLabel)
|
||||
NSLayoutConstraint.activate([
|
||||
titleLabel.topAnchor.constraint(equalTo: iconView.bottomAnchor, constant: 12),
|
||||
titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - PairingFileManager
|
||||
final class PairingFileManager {
|
||||
static let shared = PairingFileManager()
|
||||
func fetchPairingFile(presentingVC: UIViewController) -> String? {
|
||||
let fm = FileManager.default
|
||||
let filename = pairingFileName
|
||||
let documentsPath = fm.documentsDirectory.appendingPathComponent("/\(filename)")
|
||||
if fm.fileExists(atPath: documentsPath.path),
|
||||
let contents = try? String(contentsOf: documentsPath), !contents.isEmpty {
|
||||
return contents
|
||||
}
|
||||
if let url = Bundle.main.url(forResource: "ALTPairingFile", withExtension: "mobiledevicepairing"),
|
||||
fm.fileExists(atPath: url.path),
|
||||
let data = fm.contents(atPath: url.path),
|
||||
let contents = String(data: data, encoding: .utf8),
|
||||
!contents.isEmpty, !UserDefaults.standard.isPairingReset { return contents }
|
||||
if let plistString = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String,
|
||||
!plistString.isEmpty, !plistString.contains("insert pairing file here"), !UserDefaults.standard.isPairingReset { return plistString }
|
||||
|
||||
presentPairingFileAlert(on: presentingVC)
|
||||
return nil
|
||||
}
|
||||
|
||||
private func presentPairingFileAlert(on vc: UIViewController) {
|
||||
let alert = UIAlertController(title: "Pairing File", message: "Select the pairing file or select \"Help\" for help.", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "Help", style: .default) { _ in
|
||||
if let url = URL(string: "https://docs.sidestore.io/docs/advanced/pairing-file") { UIApplication.shared.open(url) }
|
||||
sleep(2); exit(0)
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
|
||||
var types = UTType.types(tag: "plist", tagClass: .filenameExtension, conformingTo: nil)
|
||||
types.append(contentsOf: UTType.types(tag: "mobiledevicepairing", tagClass: .filenameExtension, conformingTo: .data))
|
||||
types.append(.xml)
|
||||
let picker = UIDocumentPickerViewController(forOpeningContentTypes: types)
|
||||
picker.delegate = vc as? UIDocumentPickerDelegate
|
||||
picker.shouldShowFileExtensions = true
|
||||
vc.present(picker, animated: true)
|
||||
UserDefaults.standard.isPairingReset = false
|
||||
})
|
||||
vc.present(alert, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SideJITManager
|
||||
final class SideJITManager {
|
||||
static let shared = SideJITManager()
|
||||
func checkAndPromptIfNeeded(presentingVC: UIViewController) {
|
||||
guard #available(iOS 17, *), !UserDefaults.standard.sidejitenable else { return }
|
||||
DispatchQueue.global().async {
|
||||
self.isSideJITServerDetected { result in
|
||||
DispatchQueue.main.async {
|
||||
switch result {
|
||||
case .success():
|
||||
let alert = UIAlertController(title: "SideJITServer Detected", message: "Would you like to enable SideJITServer", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in UserDefaults.standard.sidejitenable = true })
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
||||
presentingVC.present(alert, animated: true)
|
||||
case .failure(_): print("Cannot find sideJITServer")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func askForNetwork() {
|
||||
let address = UserDefaults.standard.textInputSideJITServerurl ?? ""
|
||||
let SJSURL = address.isEmpty ? "http://sidejitserver._http._tcp.local:8080" : address
|
||||
URLSession.shared.dataTask(with: URL(string: "\(SJSURL)/re/")!) { data, resp, err in
|
||||
print("data: \(String(describing: data)), response: \(String(describing: resp)), error: \(String(describing: err))")
|
||||
}.resume()
|
||||
}
|
||||
|
||||
func isSideJITServerDetected(completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
let address = UserDefaults.standard.textInputSideJITServerurl ?? ""
|
||||
let SJSURL = address.isEmpty ? "http://sidejitserver._http._tcp.local:8080" : address
|
||||
guard let url = URL(string: SJSURL) else { return }
|
||||
URLSession.shared.dataTask(with: url) { _, _, error in
|
||||
if let error = error { completion(.failure(error)); return }
|
||||
completion(.success(()))
|
||||
}.resume()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import UserNotifications
|
||||
import MobileCoreServices
|
||||
import Intents
|
||||
@@ -23,7 +22,6 @@ import Roxas
|
||||
extension AppManager
|
||||
{
|
||||
static let didFetchSourceNotification = Notification.Name("io.sidestore.AppManager.didFetchSource")
|
||||
static let didUpdatePatronsNotification = Notification.Name("io.sidestore.AppManager.didUpdatePatrons")
|
||||
static let didAddSourceNotification = Notification.Name("io.sidestore.AppManager.didAddSource")
|
||||
static let didRemoveSourceNotification = Notification.Name("io.sidestore.AppManager.didRemoveSource")
|
||||
static let willInstallAppFromNewSourceNotification = Notification.Name("io.sidestore.AppManager.willInstallAppFromNewSource")
|
||||
@@ -591,34 +589,6 @@ extension AppManager
|
||||
return updateKnownSourcesOperation
|
||||
}
|
||||
|
||||
func updatePatronsIfNeeded()
|
||||
{
|
||||
// guard self.operationQueue.operations.allSatisfy({ !($0 is UpdatePatronsOperation) }) else {
|
||||
// // There's already an UpdatePatronsOperation running.
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// self.updatePatronsResult = nil
|
||||
//
|
||||
// let updatePatronsOperation = UpdatePatronsOperation()
|
||||
// updatePatronsOperation.resultHandler = { (result) in
|
||||
// do
|
||||
// {
|
||||
// try result.get()
|
||||
// self.updatePatronsResult = .success(())
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// print("Error updating Friend Zone Patrons:", error)
|
||||
// self.updatePatronsResult = .failure(error)
|
||||
// }
|
||||
//
|
||||
// NotificationCenter.default.post(name: AppManager.didUpdatePatronsNotification, object: self)
|
||||
// }
|
||||
//
|
||||
// self.run([updatePatronsOperation], context: nil)
|
||||
}
|
||||
|
||||
func updateAllSources(completion: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
self.updateSourcesResult = nil
|
||||
@@ -705,9 +675,37 @@ extension AppManager
|
||||
}
|
||||
}
|
||||
|
||||
let operation = AppOperation.install(app)
|
||||
self.perform([operation], presentingViewController: presentingViewController, group: group)
|
||||
|
||||
Task{
|
||||
var app: AppProtocol = app
|
||||
// ---- Preflight bundle ID resolution ----
|
||||
if UserDefaults.standard.customizeAppId, // only show prompt when enabled by user
|
||||
let presentingViewController {
|
||||
let originalBundleID = app.bundleIdentifier
|
||||
|
||||
let resolution = await self.resolveBundleID(
|
||||
initial: originalBundleID,
|
||||
presentingViewController: presentingViewController
|
||||
)
|
||||
|
||||
switch resolution {
|
||||
case .cancelled:
|
||||
completionHandler(.failure(OperationError.cancelled))
|
||||
group.progress.cancel()
|
||||
|
||||
case .resolved(let newBundleID):
|
||||
app = AnyApp(
|
||||
name: app.name,
|
||||
bundleIdentifier: newBundleID,
|
||||
url: app.url,
|
||||
storeApp: app.storeApp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await self.perform([.install(app)], presentingViewController: presentingViewController, group: group)
|
||||
|
||||
}
|
||||
return group
|
||||
}
|
||||
|
||||
@@ -732,10 +730,11 @@ extension AppManager
|
||||
}
|
||||
}
|
||||
|
||||
let operation = AppOperation.update(appVersion)
|
||||
assert(operation.app as AnyObject !== installedApp) // Make sure we never accidentally "update" to already installed app.
|
||||
assert(appVersion as AnyObject !== installedApp) // Make sure we never accidentally "update" to already installed app.
|
||||
|
||||
self.perform([operation], presentingViewController: presentingViewController, group: group)
|
||||
Task{
|
||||
await self.perform([.update(appVersion)], presentingViewController: presentingViewController, group: group)
|
||||
}
|
||||
|
||||
return group.progress
|
||||
}
|
||||
@@ -745,16 +744,20 @@ extension AppManager
|
||||
{
|
||||
let group = group ?? RefreshGroup()
|
||||
|
||||
let operations = installedApps.map { AppOperation.refresh($0) }
|
||||
return self.perform(operations, presentingViewController: presentingViewController, group: group)
|
||||
Task{
|
||||
await self.perform(installedApps.map { .refresh($0) }, presentingViewController: presentingViewController, group: group)
|
||||
}
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
func activate(_ installedApp: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void)
|
||||
{
|
||||
let group = RefreshGroup()
|
||||
|
||||
let operation = AppOperation.activate(installedApp)
|
||||
self.perform([operation], presentingViewController: presentingViewController, group: group)
|
||||
Task{
|
||||
await self.perform([.activate(installedApp)], presentingViewController: presentingViewController, group: group)
|
||||
}
|
||||
|
||||
group.completionHandler = { (results) in
|
||||
do
|
||||
@@ -812,8 +815,9 @@ extension AppManager
|
||||
}
|
||||
}
|
||||
|
||||
let operation = AppOperation.deactivate(installedApp)
|
||||
self.perform([operation], presentingViewController: presentingViewController, group: group)
|
||||
Task{
|
||||
await self.perform([.deactivate(installedApp)], presentingViewController: presentingViewController, group: group)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -837,8 +841,9 @@ extension AppManager
|
||||
}
|
||||
}
|
||||
|
||||
let operation = AppOperation.backup(installedApp)
|
||||
self.perform([operation], presentingViewController: presentingViewController, group: group)
|
||||
Task{
|
||||
await self.perform([.backup(installedApp)], presentingViewController: presentingViewController, group: group)
|
||||
}
|
||||
}
|
||||
|
||||
func restore(_ installedApp: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void)
|
||||
@@ -863,8 +868,9 @@ extension AppManager
|
||||
}
|
||||
}
|
||||
|
||||
let operation = AppOperation.restore(installedApp)
|
||||
self.perform([operation], presentingViewController: presentingViewController, group: group)
|
||||
Task{
|
||||
await self.perform([.restore(installedApp)], presentingViewController: presentingViewController, group: group)
|
||||
}
|
||||
}
|
||||
|
||||
func remove(_ installedApp: InstalledApp, completionHandler: @escaping (Result<Void, Error>) -> Void)
|
||||
@@ -993,7 +999,6 @@ extension AppManager
|
||||
case .failure(let error): completionHandler(.failure(error))
|
||||
case .success(let installedApp): completionHandler(.success(installedApp))
|
||||
}
|
||||
//UIApplication.shared.open(shortcutURLon, options: [:], completionHandler: nil)
|
||||
}
|
||||
installOperation.addDependency(sendAppOperation)
|
||||
|
||||
@@ -1092,7 +1097,7 @@ private extension AppManager
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) -> RefreshGroup
|
||||
private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) async -> RefreshGroup
|
||||
{
|
||||
let operations = operations.filter { self.progress(for: $0) == nil || self.progress(for: $0)?.isCancelled == true }
|
||||
|
||||
@@ -1154,38 +1159,10 @@ private extension AppManager
|
||||
|
||||
case .activate(let app) where UserDefaults.standard.isLegacyDeactivationSupported: fallthrough
|
||||
case .refresh(let app):
|
||||
// Check if backup app is installed in place of real app.
|
||||
// let altBackupUti = UTTypeCopyDeclaration(app.installedBackupAppUTI as CFString)?.takeRetainedValue() as NSDictionary?
|
||||
|
||||
// if app.certificateSerialNumber != group.context.certificate?.serialNumber ||
|
||||
// altBackupUti != nil || // why would altbackup requires reinstall? it shouldn't cause we are just renewing profiles
|
||||
// app.needsResign || // why would an app require resign during refresh? it shouldn't!
|
||||
// We need to reinstall ourselves on refresh to ensure the new provisioning profile is used
|
||||
// => mahee96: jkcoxson confirmed misagent manages profiles independently without requiring lockdownd or installd intervention, so sidestore profile renewal shouldn't require reinstall
|
||||
// app.bundleIdentifier == StoreApp.altstoreAppID
|
||||
// {
|
||||
// Resign app instead of just refreshing profiles because either:
|
||||
// * Refreshing using different certificate // when can this happen?, lets assume, refreshing with different certificate, why not just ask user to re-install manually? (probably we need re-install button)
|
||||
// * Backup app is still installed // but why? I mean the AltBackup was put in place for a reason? ie during refresh just renew appIDs don't care about the app itself.
|
||||
// * App explicitly needs resigning // when can this happen?
|
||||
// * Device is jailbroken and using AltDaemon on iOS 14.0 or later (b/c refreshing with provisioning profiles is broken)
|
||||
|
||||
// let installProgress = self._install(app, operation: operation, group: group) { (result) in
|
||||
// self.finish(operation, result: result, group: group, progress: progress)
|
||||
// }
|
||||
// progress?.addChild(installProgress, withPendingUnitCount: 80)
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Refreshing with same certificate as last time, and backup app isn't still installed,
|
||||
// so we can just refresh provisioning profiles.
|
||||
|
||||
let refreshProgress = self._refresh(app, operation: operation, group: group) { (result) in
|
||||
self.finish(operation, result: result, group: group, progress: progress)
|
||||
}
|
||||
progress?.addChild(refreshProgress, withPendingUnitCount: 80)
|
||||
// }
|
||||
|
||||
let refreshProgress = self._refresh(app, operation: operation, group: group) { (result) in
|
||||
self.finish(operation, result: result, group: group, progress: progress)
|
||||
}
|
||||
progress?.addChild(refreshProgress, withPendingUnitCount: 80)
|
||||
case .activate(let app):
|
||||
let activateProgress = self._activate(app, operation: operation, group: group) { (result) in
|
||||
self.finish(operation, result: result, group: group, progress: progress)
|
||||
@@ -1255,21 +1232,10 @@ private extension AppManager
|
||||
{
|
||||
let progress = Progress.discreteProgress(totalUnitCount: 100)
|
||||
|
||||
let context = context ?? InstallAppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context)
|
||||
let context = InstallAppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context)
|
||||
assert(context.authenticatedContext === group.context)
|
||||
|
||||
context.beginInstallationHandler = { (installedApp) in
|
||||
switch appOperation
|
||||
{
|
||||
case .update where installedApp.bundleIdentifier == StoreApp.altstoreAppID:
|
||||
// AltStore will quit before installation finishes,
|
||||
// so assume if we get this far the update will finish successfully.
|
||||
let event = AnalyticsManager.Event.updatedApp(installedApp)
|
||||
AnalyticsManager.shared.trackEvent(event)
|
||||
|
||||
default: break
|
||||
}
|
||||
|
||||
group.beginInstallationHandler?(installedApp)
|
||||
}
|
||||
|
||||
@@ -1290,20 +1256,6 @@ private extension AppManager
|
||||
}
|
||||
}
|
||||
|
||||
var verifyPledgeOperation: VerifyAppPledgeOperation?
|
||||
if let storeApp = app.storeApp
|
||||
{
|
||||
verifyPledgeOperation = VerifyAppPledgeOperation(storeApp: storeApp, presentingViewController: context.presentingViewController)
|
||||
verifyPledgeOperation?.resultHandler = { result in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error):
|
||||
context.error = error
|
||||
case .success: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Download */
|
||||
let downloadedAppURL = context.temporaryDirectory.appendingPathComponent("Cached.app")
|
||||
let downloadOperation = DownloadAppOperation(app: downloadingApp, destinationURL: downloadedAppURL, context: context)
|
||||
@@ -1315,7 +1267,8 @@ private extension AppManager
|
||||
|
||||
if cacheApp
|
||||
{
|
||||
try FileManager.default.copyItem(at: app.fileURL, to: InstalledApp.fileURL(for: app), shouldReplace: true)
|
||||
let updatedApp = AnyApp(from: app, bundleId: context.bundleIdentifier)
|
||||
try FileManager.default.copyItem(at: app.fileURL, to: InstalledApp.fileURL(for: updatedApp), shouldReplace: true)
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -1325,15 +1278,9 @@ private extension AppManager
|
||||
}
|
||||
progress.addChild(downloadOperation.progress, withPendingUnitCount: 25)
|
||||
|
||||
if let verifyPledgeOperation
|
||||
{
|
||||
downloadOperation.addDependency(verifyPledgeOperation)
|
||||
}
|
||||
|
||||
|
||||
/* Verify App */
|
||||
let permissionsMode = UserDefaults.shared.permissionCheckingDisabled ? .none : permissionReviewMode
|
||||
let verifyOperation = VerifyAppOperation(permissionsMode: permissionsMode, context: context)
|
||||
let verifyOperation = VerifyAppOperation(permissionsMode: permissionsMode, context: context, customBundleId: app.bundleIdentifier)
|
||||
verifyOperation.resultHandler = { (result) in
|
||||
do
|
||||
{
|
||||
@@ -1486,7 +1433,7 @@ private extension AppManager
|
||||
let patchAppURL = URL(string: patchAppLink)
|
||||
else { throw OperationError.invalidApp }
|
||||
|
||||
let patchApp = AnyApp(name: app.name, bundleIdentifier: app.bundleIdentifier, url: patchAppURL, storeApp: nil)
|
||||
let patchApp = AnyApp(name: app.name, bundleIdentifier: context.bundleIdentifier, url: patchAppURL, storeApp: nil)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let storyboard = UIStoryboard(name: "PatchApp", bundle: nil)
|
||||
@@ -1508,7 +1455,7 @@ private extension AppManager
|
||||
presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
presentingViewController.present(navigationController, animated: true, completion: nil)
|
||||
presentingViewController.present(navigationController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -1520,6 +1467,24 @@ private extension AppManager
|
||||
patchAppOperation.addDependency(deactivateAppsOperation)
|
||||
|
||||
|
||||
let modifyAppExBundleIdOperation = RSTAsyncBlockOperation { operation in
|
||||
if !context.useMainProfile {
|
||||
operation.finish()
|
||||
return
|
||||
}
|
||||
|
||||
if let app = context.app, let profile = context.provisioningProfiles?[context.bundleIdentifier] {
|
||||
var appexBundleIds: [String: String] = [:]
|
||||
for appex in app.appExtensions {
|
||||
appexBundleIds[appex.bundleIdentifier] = appex.bundleIdentifier.replacingOccurrences(of: app.bundleIdentifier, with: profile.bundleIdentifier)
|
||||
}
|
||||
context.appexBundleIds = appexBundleIds
|
||||
}
|
||||
operation.finish()
|
||||
|
||||
}
|
||||
modifyAppExBundleIdOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||
|
||||
/* Resign */
|
||||
let resignAppOperation = ResignAppOperation(context: context)
|
||||
resignAppOperation.resultHandler = { (result) in
|
||||
@@ -1534,6 +1499,7 @@ private extension AppManager
|
||||
}
|
||||
}
|
||||
resignAppOperation.addDependency(patchAppOperation)
|
||||
resignAppOperation.addDependency(modifyAppExBundleIdOperation)
|
||||
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
|
||||
|
||||
|
||||
@@ -1579,7 +1545,6 @@ private extension AppManager
|
||||
|
||||
// Operations picked for request
|
||||
var operations = [
|
||||
verifyPledgeOperation,
|
||||
downloadOperation,
|
||||
verifyOperation,
|
||||
removeAppExtensionsOperation,
|
||||
@@ -1587,6 +1552,7 @@ private extension AppManager
|
||||
patchAppOperation,
|
||||
refreshAnisetteDataOperation,
|
||||
fetchProvisioningProfilesOperation,
|
||||
modifyAppExBundleIdOperation,
|
||||
resignAppOperation,
|
||||
sendAppOperation,
|
||||
installOperation
|
||||
@@ -1666,8 +1632,8 @@ private extension AppManager
|
||||
|
||||
let context = AppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context)
|
||||
context.app = ALTApplication(fileURL: app.fileURL)
|
||||
|
||||
// Since this doesn't involve modifying app bundle which will cause re-install, this is safe in refresh path
|
||||
context.useMainProfile = app.useMainProfile
|
||||
// Since this doesn't involve modifying app bundle which will cause re-install, this is safe in refresh path
|
||||
//App-Extensions: Ensure DB data and disk state must match
|
||||
let dbAppEx: Set<InstalledExtension> = Set(app.appExtensions)
|
||||
let diskAppEx: Set<ALTApplication> = Set(context.app!.appExtensions)
|
||||
@@ -2126,27 +2092,6 @@ private extension AppManager
|
||||
self.scheduleExpirationWarningLocalNotification(for: installedApp)
|
||||
}
|
||||
|
||||
let event: AnalyticsManager.Event?
|
||||
|
||||
switch operation
|
||||
{
|
||||
case .install: event = .installedApp(installedApp)
|
||||
case .refresh: event = .refreshedApp(installedApp)
|
||||
case .update where installedApp.bundleIdentifier == StoreApp.altstoreAppID:
|
||||
// AltStore quits before update finishes, so we've preemptively logged this update event.
|
||||
// In case AltStore doesn't quit, such as when update has a different bundle identifier,
|
||||
// make sure we don't log this update event a second time.
|
||||
event = nil
|
||||
|
||||
case .update: event = .updatedApp(installedApp)
|
||||
case .activate, .deactivate, .backup, .restore: event = nil
|
||||
}
|
||||
|
||||
if let event = event
|
||||
{
|
||||
AnalyticsManager.shared.trackEvent(event)
|
||||
}
|
||||
|
||||
// Ask widgets to be refreshed
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
|
||||
@@ -2231,7 +2176,7 @@ private extension AppManager
|
||||
switch operation
|
||||
{
|
||||
case _ where requiresSerialQueue: fallthrough
|
||||
case is InstallAppOperation, is RefreshAppOperation, is BackupAppOperation, is VerifyAppPledgeOperation:
|
||||
case is InstallAppOperation, is RefreshAppOperation, is BackupAppOperation:
|
||||
if let installAltStoreOperation = operation as? InstallAppOperation, installAltStoreOperation.context.bundleIdentifier == StoreApp.altstoreAppID
|
||||
{
|
||||
// Add dependencies on previous serial operations in `context` to ensure re-installing AltStore goes last.
|
||||
@@ -2283,3 +2228,126 @@ private extension AppManager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum BundleIDAlertKeys {
|
||||
static var okAction: UInt8 = 0
|
||||
}
|
||||
|
||||
private func _isValidBundleID(_ value: String) -> Bool {
|
||||
let pattern = #"^[A-Za-z][A-Za-z0-9\-]*(\.[A-Za-z0-9\-]+)+$"#
|
||||
return value.range(of: pattern, options: .regularExpression) != nil
|
||||
}
|
||||
|
||||
private extension UIResponder {
|
||||
@objc func _validateBundleIDText(_ sender: UITextField) {
|
||||
let isValid = sender.text.map(_isValidBundleID) ?? false
|
||||
|
||||
sender.backgroundColor =
|
||||
isValid || sender.text?.isEmpty == true
|
||||
? .clear
|
||||
: UIColor.systemRed.withAlphaComponent(0.2)
|
||||
|
||||
if
|
||||
let alert = sender.superview?.superview as? UIAlertController,
|
||||
let okAction = objc_getAssociatedObject(alert, &BundleIDAlertKeys.okAction) as? UIAlertAction
|
||||
{
|
||||
okAction.isEnabled = isValid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private extension AppManager {
|
||||
|
||||
func _presentBundleIDOverrideDialog(
|
||||
bundleIdentifier: String,
|
||||
presentingViewController: UIViewController,
|
||||
completion: @escaping (BundleIDResolution) -> Void
|
||||
) {
|
||||
let alert = self._makeBundleIDOverrideAlert(
|
||||
initialBundleID: bundleIdentifier,
|
||||
completion: completion
|
||||
)
|
||||
|
||||
presentingViewController.present(alert, animated: true)
|
||||
}
|
||||
|
||||
func _makeBundleIDOverrideAlert(
|
||||
initialBundleID: String,
|
||||
completion: @escaping (BundleIDResolution) -> Void
|
||||
) -> UIAlertController {
|
||||
|
||||
let titleText = NSLocalizedString("AppID Customization", comment: "")
|
||||
let messageText = NSLocalizedString("Customize the AppID if required and press 'Confirm' to proceed.", comment: "")
|
||||
|
||||
let alert = UIAlertController(
|
||||
title: titleText,
|
||||
message: messageText,
|
||||
preferredStyle: .alert
|
||||
)
|
||||
|
||||
var okAction: UIAlertAction!
|
||||
|
||||
alert.addTextField { textField in
|
||||
textField.text = initialBundleID
|
||||
textField.autocapitalizationType = .none
|
||||
textField.autocorrectionType = .no
|
||||
textField.addTarget(
|
||||
nil,
|
||||
action: #selector(UIResponder._validateBundleIDText(_:)),
|
||||
for: .editingChanged
|
||||
)
|
||||
}
|
||||
|
||||
okAction = UIAlertAction(title: NSLocalizedString("Confirm", comment: ""), style: .default) { _ in
|
||||
completion(.resolved(alert.textFields?.first?.text ?? initialBundleID))
|
||||
}
|
||||
|
||||
okAction.isEnabled = _isValidBundleID(initialBundleID)
|
||||
|
||||
let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { _ in
|
||||
completion(.cancelled)
|
||||
}
|
||||
|
||||
alert.addAction(cancelAction)
|
||||
alert.addAction(okAction)
|
||||
|
||||
objc_setAssociatedObject(
|
||||
alert,
|
||||
&BundleIDAlertKeys.okAction,
|
||||
okAction,
|
||||
.OBJC_ASSOCIATION_ASSIGN
|
||||
)
|
||||
|
||||
return alert
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- Part 1: Add async resolver ----
|
||||
|
||||
private extension AppManager {
|
||||
|
||||
enum BundleIDResolution {
|
||||
case resolved(String)
|
||||
case cancelled
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func resolveBundleID(
|
||||
initial: String,
|
||||
presentingViewController: UIViewController
|
||||
) async -> BundleIDResolution {
|
||||
|
||||
await withCheckedContinuation { continuation in
|
||||
let alert = self._makeBundleIDOverrideAlert(
|
||||
initialBundleID: initial
|
||||
) { result in
|
||||
continuation.resume(returning: result)
|
||||
}
|
||||
|
||||
presentingViewController.present(alert, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,9 +166,11 @@ class MyAppsViewController: UICollectionViewController, PeekPopPreviewing
|
||||
@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)
|
||||
// added isMinimuxerStatusCheckEnabled to forcefully ignore minimuxer status if status check is disabled in settings
|
||||
guard !UserDefaults.standard.isMinimuxerStatusCheckEnabled || minimuxer.ready() else {
|
||||
ToastView(error: (OperationError.noWiFi as NSError).withLocalizedTitle("No Wi-Fi or VPN!")).show(in: self)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -542,11 +544,6 @@ private extension MyAppsViewController
|
||||
{
|
||||
print("[ALTLog] Failed to fetch updates:", error)
|
||||
}
|
||||
|
||||
if let patreonAccount = DatabaseManager.shared.patreonAccount(), patreonAccount.isAltStorePatron, PatreonAPI.shared.isAuthenticated
|
||||
{
|
||||
self.dataSource.predicate = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1485,15 +1482,6 @@ private extension MyAppsViewController
|
||||
guard minimuxerStatus else { return }
|
||||
}
|
||||
|
||||
if #available(iOS 17, *), !sidejitenabled {
|
||||
let error = OperationError.tooNewError as NSError
|
||||
let localizedError = error.withLocalizedTitle("No iOS 17 On Device JIT!")
|
||||
|
||||
ToastView(error: localizedError, opensLog: true).show(in: self)
|
||||
AppManager.shared.log(error, operation: .enableJIT, app: installedApp)
|
||||
return
|
||||
}
|
||||
|
||||
AppManager.shared.enableJIT(for: installedApp) { result in
|
||||
DispatchQueue.main.async {
|
||||
switch result {
|
||||
|
||||
@@ -84,71 +84,87 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
||||
self.finish(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
// Sign In
|
||||
self.signIn() { (result) in
|
||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||
|
||||
switch result
|
||||
|
||||
Task {
|
||||
// try to use cached session
|
||||
if
|
||||
let certificate = Keychain.shared.certificate,
|
||||
let session = Keychain.shared.session,
|
||||
let team = Keychain.shared.team
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success((let account, let session)):
|
||||
self.context.session = session
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
// Fetch Team
|
||||
self.fetchTeam(for: account, session: session) { (result) in
|
||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success(let team):
|
||||
self.context.team = team
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
// Fetch Certificate
|
||||
self.fetchCertificate(for: team, session: session) { (result) in
|
||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success(let certificate):
|
||||
self.context.certificate = certificate
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
// Register Device
|
||||
self.registerCurrentDevice(for: team, session: session) { (result) in
|
||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success:
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
// Save account/team to disk.
|
||||
self.save(team) { (result) in
|
||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): self.finish(.failure(error))
|
||||
case .success:
|
||||
// Must cache App IDs _after_ saving account/team to disk.
|
||||
self.cacheAppIDs(team: team, session: session) { (result) in
|
||||
let result = result.map { _ in (team, certificate, session) }
|
||||
self.finish(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if session.anisetteData.date.timeIntervalSinceNow < -40.0 {
|
||||
let anisetteData = try await withUnsafeThrowingContinuation { (c: UnsafeContinuation<ALTAnisetteData, any Error>) in
|
||||
let fetchAnisetteDataOperation = FetchAnisetteDataOperation(context: self.context)
|
||||
fetchAnisetteDataOperation.resultHandler = { (result) in
|
||||
c.resume(with: result)
|
||||
}
|
||||
self.operationQueue.addOperation(fetchAnisetteDataOperation)
|
||||
}
|
||||
session.anisetteData = anisetteData
|
||||
}
|
||||
self.context.team = team
|
||||
self.context.session = session
|
||||
self.context.certificate = certificate
|
||||
self.finish(.success((team, certificate, session)))
|
||||
return
|
||||
}
|
||||
|
||||
// new login
|
||||
do {
|
||||
let (account, session) = try await withUnsafeThrowingContinuation { c in
|
||||
self.signIn() { (result) in
|
||||
c.resume(with: result)
|
||||
}
|
||||
}
|
||||
self.context.session = session
|
||||
self.progress.completedUnitCount += 1
|
||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||
|
||||
let team = try await withUnsafeThrowingContinuation { c in
|
||||
self.fetchTeam(for: account, session: session) { (result) in
|
||||
c.resume(with: result)
|
||||
}
|
||||
}
|
||||
self.context.team = team
|
||||
self.progress.completedUnitCount += 1
|
||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||
|
||||
let certificate = try await withUnsafeThrowingContinuation { c in
|
||||
self.fetchCertificate(for: team, session: session) { (result) in
|
||||
c.resume(with: result)
|
||||
}
|
||||
}
|
||||
self.context.certificate = certificate
|
||||
self.progress.completedUnitCount += 1
|
||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||
|
||||
let _ = try await withUnsafeThrowingContinuation { c in
|
||||
self.registerCurrentDevice(for: team, session: session) { (result) in
|
||||
c.resume(with: result)
|
||||
}
|
||||
}
|
||||
self.progress.completedUnitCount += 1
|
||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||
|
||||
try await withUnsafeThrowingContinuation { c in
|
||||
self.save(team) { (result) in
|
||||
c.resume(with: result)
|
||||
}
|
||||
}
|
||||
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
|
||||
|
||||
try await withUnsafeThrowingContinuation { c in
|
||||
self.cacheAppIDs(team: team, session: session) { (result) in
|
||||
c.resume(with: result)
|
||||
}
|
||||
}
|
||||
Keychain.shared.team = team
|
||||
Keychain.shared.certificate = certificate
|
||||
Keychain.shared.session = session
|
||||
self.finish(.success((team, certificate, session)))
|
||||
|
||||
} catch {
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,6 +375,29 @@ private extension AuthenticationOperation
|
||||
}
|
||||
}
|
||||
|
||||
if let adsid = Keychain.shared.appleIDAdsid, let xcodeToken = Keychain.shared.appleIDXcodeToken {
|
||||
Logger.sideload.notice("Authenticating Apple ID with tokens...")
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var shouldContinue = true
|
||||
Task {
|
||||
defer {
|
||||
semaphore.signal()
|
||||
}
|
||||
do {
|
||||
let (account, session) = try await self.authenticateWithToken(adsid: adsid, xcodeToken: xcodeToken)
|
||||
completionHandler(.success((account, session)))
|
||||
shouldContinue = false
|
||||
} catch {
|
||||
Logger.sideload.notice("Authentication failed with token. Fall back to email and password login: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
semaphore.wait()
|
||||
if !shouldContinue {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword
|
||||
{
|
||||
Logger.sideload.notice("Authenticating Apple ID...")
|
||||
@@ -384,6 +423,25 @@ private extension AuthenticationOperation
|
||||
}
|
||||
}
|
||||
|
||||
func authenticateWithToken(adsid: String, xcodeToken: String) async throws -> (ALTAccount, ALTAppleAPISession) {
|
||||
let anisetteData = try await withUnsafeThrowingContinuation { (c: UnsafeContinuation<ALTAnisetteData, any Error>) in
|
||||
let fetchAnisetteDataOperation = FetchAnisetteDataOperation(context: self.context)
|
||||
fetchAnisetteDataOperation.resultHandler = { (result) in
|
||||
c.resume(with: result)
|
||||
}
|
||||
self.operationQueue.addOperation(fetchAnisetteDataOperation)
|
||||
}
|
||||
|
||||
let session = ALTAppleAPISession(dsid: adsid, authToken: xcodeToken, anisetteData: anisetteData)
|
||||
let account = try await withUnsafeThrowingContinuation { (c: UnsafeContinuation<ALTAccount, any Error>) in
|
||||
ALTAppleAPI.shared.fetchAccount2(session: session) { result in
|
||||
c.resume(with: result)
|
||||
}
|
||||
}
|
||||
|
||||
return (account, session)
|
||||
}
|
||||
|
||||
func authenticate(appleID: String, password: String, completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Swift.Error>) -> Void)
|
||||
{
|
||||
self.appleIDEmailAddress = appleID
|
||||
@@ -444,6 +502,8 @@ private extension AuthenticationOperation
|
||||
verificationHandler: verificationHandler) { (account, session, error) in
|
||||
if let account = account, let session = session
|
||||
{
|
||||
Keychain.shared.appleIDAdsid = session.dsid
|
||||
Keychain.shared.appleIDXcodeToken = session.authToken
|
||||
completionHandler(.success((account, session)))
|
||||
}
|
||||
else
|
||||
@@ -747,3 +807,30 @@ extension AuthenticationOperation
|
||||
self.submitCodeAction?.isEnabled = (textField.text ?? "").count == 6
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension ALTAppleAPI {
|
||||
func fetchAccount2(session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAccount, Error>) -> Void)
|
||||
{
|
||||
let url = URL(string: "viewDeveloper.action", relativeTo: self.baseURL)!
|
||||
|
||||
self.sendRequest(with: url, additionalParameters: nil, session: session, team: nil) { (responseDictionary, requestError) in
|
||||
do
|
||||
{
|
||||
guard let responseDictionary = responseDictionary else { throw requestError ?? ALTAppleAPIError.unknown() }
|
||||
|
||||
guard let account = try self.processResponse(responseDictionary, parseHandler: { () -> Any? in
|
||||
guard let dictionary = responseDictionary["developer"] as? [String: Any] else { return nil }
|
||||
let account = ALTAccount(responseDictionary: dictionary)
|
||||
return account
|
||||
}, resultCodeHandler: nil) as? ALTAccount else {
|
||||
throw ALTAppleAPIError.unknown()
|
||||
}
|
||||
|
||||
completionHandler(.success(account))
|
||||
} catch {
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,10 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
|
||||
self.finish(.failure(RefreshError(.noInstalledApps)))
|
||||
return
|
||||
}
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
|
||||
if UserDefaults.standard.enableEMPforWireguard {
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
}
|
||||
target_minimuxer_address()
|
||||
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
|
||||
do {
|
||||
|
||||
@@ -38,7 +38,7 @@ final class DownloadAppOperation: ResultOperation<ALTApplication>
|
||||
self.context = context
|
||||
|
||||
self.appName = app.name
|
||||
self.bundleIdentifier = app.bundleIdentifier
|
||||
self.bundleIdentifier = context.bundleIdentifier
|
||||
self.sourceURL = app.url
|
||||
self.destinationURL = destinationURL
|
||||
|
||||
@@ -77,7 +77,7 @@ final class DownloadAppOperation: ResultOperation<ALTApplication>
|
||||
guard let latestVersion = storeApp.latestAvailableVersion else {
|
||||
let failureReason = String(format: NSLocalizedString("The latest version of %@ could not be determined.", comment: ""), self.appName)
|
||||
throw OperationError.unknown(failureReason: failureReason)
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to download latest _available_ version, and fall back to older versions if necessary.
|
||||
appVersion = latestVersion
|
||||
@@ -224,12 +224,6 @@ private extension DownloadAppOperation
|
||||
fileURL = sourceURL
|
||||
self.progress.completedUnitCount += 3
|
||||
}
|
||||
else if let host = sourceURL.host, host.lowercased().hasSuffix("patreon.com") && sourceURL.path.lowercased() == "/file"
|
||||
{
|
||||
// Patreon app
|
||||
fileURL = try await downloadPatreonApp(from: sourceURL)
|
||||
self.printWithTid("downloadPatreonApp: completed at \(fileURL.path)")
|
||||
}
|
||||
else
|
||||
{
|
||||
// Regular app
|
||||
@@ -323,107 +317,6 @@ private extension DownloadAppOperation
|
||||
self.printWithTid("download started: \(downloadURL)")
|
||||
}
|
||||
}
|
||||
|
||||
func downloadPatreonApp(from patreonURL: URL) async throws -> URL
|
||||
{
|
||||
guard !UserDefaults.shared.skipPatreonDownloads else {
|
||||
// Skip all hacks, take user straight to Patreon post.
|
||||
return try await downloadFromPatreonPost()
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
// User is pledged to this app, attempt to download.
|
||||
|
||||
let fileURL = try await downloadFile(from: patreonURL)
|
||||
return fileURL
|
||||
}
|
||||
catch URLError.noPermissionsToReadFile
|
||||
{
|
||||
guard let presentingViewController = self.context.presentingViewController else { throw OperationError.pledgeRequired(appName: self.appName) }
|
||||
|
||||
// Attempt to sign-in again in case our Patreon session has expired.
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
PatreonAPI.shared.authenticate(presentingViewController: presentingViewController) { result in
|
||||
do
|
||||
{
|
||||
let account = try result.get()
|
||||
try account.managedObjectContext?.save()
|
||||
|
||||
continuation.resume()
|
||||
}
|
||||
catch
|
||||
{
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
// Success, so try to download once more now that we're definitely authenticated.
|
||||
|
||||
let fileURL = try await downloadFile(from: patreonURL)
|
||||
return fileURL
|
||||
}
|
||||
catch URLError.noPermissionsToReadFile
|
||||
{
|
||||
// We know authentication succeeded, so failure must mean user isn't patron/on the correct tier,
|
||||
// or that our hacky workaround for downloading Patreon attachments has failed.
|
||||
// Either way, taking them directly to the post serves as a decent fallback.
|
||||
|
||||
return try await downloadFromPatreonPost()
|
||||
}
|
||||
}
|
||||
|
||||
func downloadFromPatreonPost() async throws -> URL
|
||||
{
|
||||
guard let presentingViewController = self.context.presentingViewController else { throw OperationError.pledgeRequired(appName: self.appName) }
|
||||
|
||||
let downloadURL: URL
|
||||
|
||||
if let components = URLComponents(url: patreonURL, resolvingAgainstBaseURL: false),
|
||||
let postItem = components.queryItems?.first(where: { $0.name == "h" }),
|
||||
let postID = postItem.value,
|
||||
let patreonPostURL = URL(string: "https://www.patreon.com/posts/" + postID)
|
||||
{
|
||||
downloadURL = patreonPostURL
|
||||
}
|
||||
else
|
||||
{
|
||||
downloadURL = patreonURL
|
||||
}
|
||||
|
||||
return try await downloadFromPatreon(downloadURL, presentingViewController: presentingViewController)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func downloadFromPatreon(_ patreonURL: URL, presentingViewController: UIViewController) async throws -> URL
|
||||
{
|
||||
let webViewController = WebViewController(url: patreonURL)
|
||||
webViewController.delegate = self
|
||||
webViewController.webView.navigationDelegate = self
|
||||
|
||||
let navigationController = UINavigationController(rootViewController: webViewController)
|
||||
presentingViewController.present(navigationController, animated: true)
|
||||
|
||||
let downloadURL: URL
|
||||
|
||||
do
|
||||
{
|
||||
defer {
|
||||
navigationController.dismiss(animated: true)
|
||||
}
|
||||
|
||||
downloadURL = try await withCheckedThrowingContinuation { continuation in
|
||||
self.downloadPatreonAppContinuation = continuation
|
||||
}
|
||||
}
|
||||
|
||||
let fileURL = try await downloadFile(from: downloadURL)
|
||||
return fileURL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ struct OperationError: ALTLocalizedError {
|
||||
case .unknownResult: return NSLocalizedString("The operation returned an unknown result.", comment: "")
|
||||
case .timedOut: return NSLocalizedString("The operation timed out.", comment: "")
|
||||
case .notAuthenticated: return NSLocalizedString("You are not signed in.", comment: "")
|
||||
case .unknownUDID: return NSLocalizedString("SideStore could not determine this device's UDID.", comment: "")
|
||||
case .unknownUDID: return NSLocalizedString("SideStore could not determine this device's UDID. Please replace your pairing using iloader.", comment: "")
|
||||
case .invalidApp: return NSLocalizedString("The app is in an invalid format.", 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: "")
|
||||
@@ -220,16 +220,16 @@ struct OperationError: ALTLocalizedError {
|
||||
case .openAppFailed:
|
||||
let appName = self.appName ?? NSLocalizedString("The app", comment: "")
|
||||
return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), appName)
|
||||
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 .noWiFi: return NSLocalizedString("You do not appear to be connected to Wi-Fi and/or LocalDevVPN!\nSideStore cannot install or refresh applications without Wi-Fi and LocalDevVPN. If both are connected, replace your pairing with iloader.", comment: "")
|
||||
case .tooNewError: return NSLocalizedString("iOS 17.0-17.3.1 changed how JIT is enabled so SideStore cannot enable JIT without SideJITServer on these versions, sorry for any inconvenience.", comment: "")
|
||||
case .unableToConnectSideJIT: return NSLocalizedString("Unable to connect to SideJITServer. Please check that you are on the same Wi-Fi of and your Firewall has been set correctly on your server.", comment: "")
|
||||
case .unableToRespondSideJITDevice: return NSLocalizedString("SideJITServer is unable to connect to your iDevice. Please make sure you have paired your iDevice by running 'SideJITServer -y', or try refreshing SideJITServer from Settings.", comment: "")
|
||||
case .wrongSideJITIP: return NSLocalizedString("Incorrect SideJITServer IP. Please make sure that you are on the same Wi-Fi 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 while getting anisette data from a V1 server: %@. Try using another anisette server.", comment: "")
|
||||
case .provisioningError: return NSLocalizedString("An error occurred while provisioning: %@ %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
||||
case .anisetteV3Error: return NSLocalizedString("An error occurred while 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 the cache: %@", comment: "")
|
||||
case .SideJITIssue: return NSLocalizedString("An error occurred while using SideJIT: %@", comment: "")
|
||||
|
||||
case .refreshAppFailed:
|
||||
@@ -260,7 +260,7 @@ struct OperationError: ALTLocalizedError {
|
||||
var recoverySuggestion: String? {
|
||||
switch self.code
|
||||
{
|
||||
case .noWiFi: return NSLocalizedString("Make sure the VPN is toggled on and you are connected to any WiFi network!", comment: "")
|
||||
case .noWiFi: return NSLocalizedString("Make sure LocalDevVPN is connected and that you are connected to any Wi-Fi network!", comment: "")
|
||||
case .serverNotFound: return NSLocalizedString("Make sure you're on the same Wi-Fi network as a computer running AltServer, or try connecting this device to your computer via USB.", comment: "")
|
||||
case .maximumAppIDLimitReached:
|
||||
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
|
||||
@@ -308,9 +308,9 @@ extension MinimuxerError: LocalizedError {
|
||||
case .NoDevice:
|
||||
return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
||||
case .NoConnection:
|
||||
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: "")
|
||||
return NSLocalizedString("Unable to connect to the device, make sure LocalDevVPN is enabled and you're connected to Wi-Fi. This could mean an invalid pairing.", comment: "")
|
||||
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 iloader to replace it.", comment: "")
|
||||
|
||||
case .CreateDebug:
|
||||
return self.createService(name: "debug")
|
||||
@@ -338,7 +338,7 @@ extension MinimuxerError: LocalizedError {
|
||||
case .CreateAfc:
|
||||
return self.createService(name: "AFC")
|
||||
case .RwAfc:
|
||||
return NSLocalizedString("AFC was unable to manage files on the device. This usually means an invalid pairing.", comment: "")
|
||||
return NSLocalizedString("AFC was unable to manage files on the device. Ensure Wi-Fi and LocalDevVPN are connected. If they both are, replace your pairing using iloader.", comment: "")
|
||||
case .InstallApp(let message):
|
||||
return NSLocalizedString("Unable to install the app: \(message.toString())", comment: "")
|
||||
case .UninstallApp:
|
||||
@@ -350,6 +350,38 @@ extension MinimuxerError: LocalizedError {
|
||||
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||
case .ProfileRemove:
|
||||
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||
case .CreateLockdown:
|
||||
return NSLocalizedString("Unable to connect to lockdown", comment: "")
|
||||
case .CreateCoreDevice:
|
||||
return NSLocalizedString("Unable to connect to core device proxy", comment: "")
|
||||
case .CreateSoftwareTunnel:
|
||||
return NSLocalizedString("Unable to create software tunnel", comment: "")
|
||||
case .CreateRemoteServer:
|
||||
return NSLocalizedString("Unable to connect to remote server", comment: "")
|
||||
case .CreateProcessControl:
|
||||
return NSLocalizedString("Unable to connect to process control", comment: "")
|
||||
case .GetLockdownValue:
|
||||
return NSLocalizedString("Unable to get value from lockdown", comment: "")
|
||||
case .Connect:
|
||||
return NSLocalizedString("Unable to connect to TCP port", comment: "")
|
||||
case .Close:
|
||||
return NSLocalizedString("Unable to close TCP port", comment: "")
|
||||
case .XpcHandshake:
|
||||
return NSLocalizedString("Unable to get services from XPC", comment: "")
|
||||
case .NoService:
|
||||
return NSLocalizedString("Device did not contain service", comment: "")
|
||||
case .InvalidProductVersion:
|
||||
return NSLocalizedString("Service version was in an unexpected format", comment: "")
|
||||
case .CreateFolder:
|
||||
return NSLocalizedString("Unable to create DDI folder", comment: "")
|
||||
case .DownloadImage:
|
||||
return NSLocalizedString("Unable to download DDI", comment: "")
|
||||
case .ImageLookup:
|
||||
return NSLocalizedString("Unable to lookup DDI images", comment: "")
|
||||
case .ImageRead:
|
||||
return NSLocalizedString("Unable to read images to memory", comment: "")
|
||||
case .Mount:
|
||||
return NSLocalizedString("Mount failed", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSoc
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.calendar = Calendar(identifier: .gregorian)
|
||||
formatter.timeZone = TimeZone.current
|
||||
formatter.timeZone = TimeZone.init(secondsFromGMT: 0)
|
||||
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
|
||||
let dateString = formatter.string(from: Date())
|
||||
formattedJSON["date"] = dateString
|
||||
|
||||
@@ -54,7 +54,8 @@ class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioni
|
||||
Logger.sideload.notice("Fetching provisioning profiles for app \(self.context.bundleIdentifier, privacy: .public)...")
|
||||
|
||||
self.progress.totalUnitCount = Int64(1 + app.appExtensions.count)
|
||||
|
||||
let effectiveBundleId = self.context.bundleIdentifier
|
||||
|
||||
self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in
|
||||
do
|
||||
{
|
||||
@@ -62,25 +63,27 @@ class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioni
|
||||
|
||||
let profile = try result.get()
|
||||
|
||||
var profiles = [app.bundleIdentifier: profile]
|
||||
var profiles = [effectiveBundleId: profile]
|
||||
var error: Error?
|
||||
|
||||
let dispatchGroup = DispatchGroup()
|
||||
|
||||
for appExtension in app.appExtensions
|
||||
{
|
||||
dispatchGroup.enter()
|
||||
|
||||
self.prepareProvisioningProfile(for: appExtension, parentApp: app, team: team, session: session) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let e): error = e
|
||||
case .success(let profile): profiles[appExtension.bundleIdentifier] = profile
|
||||
if !self.context.useMainProfile {
|
||||
for appExtension in app.appExtensions
|
||||
{
|
||||
dispatchGroup.enter()
|
||||
|
||||
self.prepareProvisioningProfile(for: appExtension, parentApp: app, team: team, session: session) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let e): error = e
|
||||
case .success(let profile): profiles[appExtension.bundleIdentifier] = profile
|
||||
}
|
||||
|
||||
dispatchGroup.leave()
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
}
|
||||
|
||||
dispatchGroup.leave()
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,19 +221,30 @@ extension FetchProvisioningProfilesOperation
|
||||
// Or, if the app _is_ installed but with a different team, we need to create a new
|
||||
// bundle identifier anyway to prevent collisions with the previous team.
|
||||
let parentBundleID = parentApp?.bundleIdentifier ?? app.bundleIdentifier
|
||||
let effectiveParentBundleID = self.context.bundleIdentifier
|
||||
|
||||
let updatedParentBundleID: String
|
||||
|
||||
|
||||
if app.isAltStoreApp
|
||||
{
|
||||
// Use legacy bundle ID format for AltStore (and its extensions).
|
||||
updatedParentBundleID = parentBundleID + "." + team.identifier // Append just team identifier to make it harder to track.
|
||||
updatedParentBundleID = effectiveParentBundleID + "." + team.identifier // Append just team identifier to make it harder to track.
|
||||
}
|
||||
else
|
||||
{
|
||||
updatedParentBundleID = parentBundleID + "." + team.identifier // Append just team identifier to make it harder to track.
|
||||
updatedParentBundleID = effectiveParentBundleID + "." + team.identifier // Append just team identifier to make it harder to track.
|
||||
}
|
||||
|
||||
if let parentApp = parentApp,
|
||||
app.bundleIdentifier.hasPrefix(parentBundleID + ".")
|
||||
{
|
||||
let suffix = String(app.bundleIdentifier.dropFirst(parentBundleID.count))
|
||||
bundleID = updatedParentBundleID + suffix
|
||||
}
|
||||
else
|
||||
{
|
||||
bundleID = updatedParentBundleID
|
||||
}
|
||||
|
||||
bundleID = app.bundleIdentifier.replacingOccurrences(of: parentBundleID, with: updatedParentBundleID)
|
||||
}
|
||||
|
||||
let preferredName: String
|
||||
|
||||
@@ -177,7 +177,6 @@ final class FetchSourceOperation: ResultOperation<Source>
|
||||
}
|
||||
|
||||
try self.verify(source, response: response)
|
||||
try self.verifyPledges(for: source, in: childContext)
|
||||
|
||||
try childContext.save()
|
||||
|
||||
@@ -264,63 +263,6 @@ private extension FetchSourceOperation
|
||||
}
|
||||
}
|
||||
|
||||
func verifyPledges(for source: Source, in context: NSManagedObjectContext) throws
|
||||
{
|
||||
guard let patreonURL = source.patreonURL, let patreonAccount = DatabaseManager.shared.patreonAccount(in: context) else { return }
|
||||
|
||||
let normalizedPatreonURL = try patreonURL.normalized()
|
||||
|
||||
guard let pledge = patreonAccount.pledges.first(where: { pledge in
|
||||
do
|
||||
{
|
||||
let normalizedCampaignURL = try pledge.campaignURL.normalized()
|
||||
return normalizedCampaignURL == normalizedPatreonURL
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.main.error("Failed to normalize Patreon URL \(pledge.campaignURL, privacy: .public). \(error.localizedDescription, privacy: .public)")
|
||||
return false
|
||||
}
|
||||
}) else { return }
|
||||
|
||||
// User is pledged to this source's Patreon, so check which apps they're pledged to.
|
||||
|
||||
// We only assign `isPledged = true` because false is already the default,
|
||||
// and only one check needs to be true for isPledged to be true.
|
||||
|
||||
for app in source.apps where app.isPledgeRequired
|
||||
{
|
||||
if let requiredAppPledge = app.pledgeAmount
|
||||
{
|
||||
if pledge.amount >= requiredAppPledge
|
||||
{
|
||||
app.isPledged = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if let tierIDs = app._tierIDs
|
||||
{
|
||||
let tier = pledge.tiers.first { tierIDs.contains($0.identifier) }
|
||||
if tier != nil
|
||||
{
|
||||
app.isPledged = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if let rewardID = app._rewardID
|
||||
{
|
||||
let reward = pledge.rewards.first { $0.identifier == rewardID }
|
||||
if reward != nil
|
||||
{
|
||||
app.isPledged = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifySourceNotBlocked(_ source: Source, response: URLResponse?) throws
|
||||
{
|
||||
guard let blockedSources = UserDefaults.shared.blockedSources else { return }
|
||||
|
||||
@@ -13,8 +13,6 @@ import AltSign
|
||||
import Roxas
|
||||
import minimuxer
|
||||
|
||||
let shortcutURLonDelay = URL(string: "shortcuts://run-shortcut?name=TurnOnDataDelay")!
|
||||
|
||||
@objc(InstallAppOperation)
|
||||
final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
{
|
||||
@@ -74,6 +72,8 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
}
|
||||
|
||||
installedApp.update(resignedApp: resignedApp, certificateSerialNumber: certificate.serialNumber, storeBuildVersion: storeBuildVersion)
|
||||
installedApp.useMainProfile = self.context.useMainProfile
|
||||
|
||||
installedApp.needsResign = false
|
||||
|
||||
if let team = DatabaseManager.shared.activeTeam(in: backgroundContext)
|
||||
@@ -98,22 +98,22 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
let resignedParentBundleID = resignedApp.bundleIdentifier
|
||||
|
||||
let resignedBundleID = appExtension.bundleIdentifier
|
||||
let originalBundleID = resignedBundleID.replacingOccurrences(of: resignedParentBundleID, with: parentBundleID)
|
||||
let appExBundleID = resignedBundleID.replacingOccurrences(of: resignedParentBundleID, with: parentBundleID)
|
||||
|
||||
print("`parentBundleID`: \(parentBundleID)")
|
||||
print("`resignedParentBundleID`: \(resignedParentBundleID)")
|
||||
print("`resignedBundleID`: \(resignedBundleID)")
|
||||
print("`originalBundleID`: \(originalBundleID)")
|
||||
print("`appExBundleID`: \(appExBundleID)")
|
||||
print("`resignedAppExBundleID`: \(resignedBundleID)")
|
||||
|
||||
let installedExtension: InstalledExtension
|
||||
|
||||
if let appExtension = installedApp.appExtensions.first(where: { $0.bundleIdentifier == originalBundleID })
|
||||
if let appExtension = installedApp.appExtensions.first(where: { $0.bundleIdentifier == appExBundleID })
|
||||
{
|
||||
installedExtension = appExtension
|
||||
}
|
||||
else
|
||||
{
|
||||
installedExtension = InstalledExtension(resignedAppExtension: appExtension, originalBundleIdentifier: originalBundleID, context: backgroundContext)
|
||||
installedExtension = InstalledExtension(resignedAppExtension: appExtension, originalBundleIdentifier: appExBundleID, context: backgroundContext)
|
||||
}
|
||||
|
||||
installedExtension.update(resignedAppExtension: appExtension)
|
||||
@@ -206,10 +206,6 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
let alert = UIAlertController(title: "Finish Refresh", message: "Please reopen SideStore after the process is finished.To finish refreshing, SideStore must be moved to the background. To do this, you can either go to the Home Screen manually or by hitting Continue. Please reopen SideStore after doing this.", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default, handler: { _ in
|
||||
print("Going home")
|
||||
// Cell Shortcut
|
||||
UIApplication.shared.open(shortcutURLonDelay, options: [:]) { _ in
|
||||
print("Cell OFF Shortcut finished execution.")}
|
||||
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
}))
|
||||
|
||||
@@ -226,8 +222,6 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cell Shortcut
|
||||
UIApplication.shared.open(shortcutURLonDelay, options: [:]) { _ in print("Cell OFF Shortcut finished execution.")}
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ class AppOperationContext
|
||||
|
||||
var app: ALTApplication?
|
||||
var provisioningProfiles: [String: ALTProvisioningProfile]?
|
||||
var appexBundleIds: [String: String]?
|
||||
var useMainProfile = false
|
||||
|
||||
var isFinished = false
|
||||
|
||||
|
||||
@@ -212,6 +212,7 @@ private extension PatchAppOperation
|
||||
#if targetEnvironment(simulator)
|
||||
throw PatchAppError.unsupportedOperatingSystemVersion(ProcessInfo.processInfo.operatingSystemVersion)
|
||||
#else
|
||||
|
||||
let spotlightPath = "Applications/Spotlight.app/Spotlight"
|
||||
let spotlightFileURL = self.patchDirectory.appendingPathComponent(spotlightPath)
|
||||
|
||||
|
||||
@@ -136,7 +136,11 @@ final class RemoveAppExtensionsOperation: ResultOperation<Void>
|
||||
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
|
||||
self.finish(.failure(OperationError.cancelled))
|
||||
}))
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions (Use Main Profile)", comment: ""), style: .default) { (action) in
|
||||
self.context.useMainProfile = true
|
||||
self.finish(.success(()))
|
||||
})
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions (Register App ID for Each Extension)", comment: ""), style: .default) { (action) in
|
||||
self.finish(.success(()))
|
||||
})
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in
|
||||
|
||||
@@ -55,7 +55,8 @@ final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
let prepareAppProgress = Progress.discreteProgress(totalUnitCount: 2)
|
||||
self.progress.addChild(prepareAppProgress, withPendingUnitCount: 3)
|
||||
|
||||
let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles) { (result) in
|
||||
let effectiveBundleId = self.context.bundleIdentifier
|
||||
let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles, appexBundleIds: context.appexBundleIds ?? [:]) { (result) in
|
||||
guard let appBundleURL = self.process(result) else { return }
|
||||
|
||||
// Resign app bundle
|
||||
@@ -65,7 +66,13 @@ final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
// Finish
|
||||
do
|
||||
{
|
||||
let destinationURL = InstalledApp.refreshedIPAURL(for: app)
|
||||
let updatedApp = AnyApp(
|
||||
name: app.name,
|
||||
bundleIdentifier: effectiveBundleId,
|
||||
url: app.fileURL,
|
||||
storeApp: app.storeApp
|
||||
)
|
||||
let destinationURL = InstalledApp.refreshedIPAURL(for: updatedApp)
|
||||
try FileManager.default.copyItem(at: resignedURL, to: destinationURL, shouldReplace: true)
|
||||
print("Successfully resigned app to \(destinationURL.absoluteString)")
|
||||
|
||||
@@ -107,22 +114,26 @@ final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
|
||||
private extension ResignAppOperation
|
||||
{
|
||||
func prepareAppBundle(for app: ALTApplication, profiles: [String: ALTProvisioningProfile], completionHandler: @escaping (Result<URL, Error>) -> Void) -> Progress
|
||||
func prepareAppBundle(for app: ALTApplication, profiles: [String: ALTProvisioningProfile], appexBundleIds: [String: String], completionHandler: @escaping (Result<URL, Error>) -> Void) -> Progress
|
||||
{
|
||||
let progress = Progress.discreteProgress(totalUnitCount: 1)
|
||||
|
||||
let bundleIdentifier = app.bundleIdentifier
|
||||
|
||||
let bundleIdentifier = context.bundleIdentifier
|
||||
let openURL = InstalledApp.openAppURL(for: app)
|
||||
|
||||
let fileURL = app.fileURL
|
||||
|
||||
func prepare(_ bundle: Bundle, additionalInfoDictionaryValues: [String: Any] = [:]) throws
|
||||
|
||||
func prepare(_ bundle: Bundle, bundleID identifier: String?, additionalInfoDictionaryValues: [String: Any] = [:]) throws
|
||||
{
|
||||
guard let identifier = bundle.bundleIdentifier else { throw ALTError(.missingAppBundle) }
|
||||
guard let profile = profiles[identifier] else { throw ALTError(.missingProvisioningProfile) }
|
||||
guard let identifier else { throw ALTError(.missingAppBundle) }
|
||||
guard let profile = context.useMainProfile ? profiles.values.first : profiles[identifier] else { throw ALTError(.missingProvisioningProfile) }
|
||||
guard var infoDictionary = bundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) }
|
||||
|
||||
infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
|
||||
if let forcedBundleIdentifier = appexBundleIds[identifier] {
|
||||
infoDictionary[kCFBundleIdentifierKey as String] = forcedBundleIdentifier
|
||||
} else {
|
||||
infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
|
||||
}
|
||||
|
||||
infoDictionary[Bundle.Info.altBundleID] = identifier
|
||||
infoDictionary[Bundle.Info.devicePairingString] = "<insert pairing file here>"
|
||||
infoDictionary.removeValue(forKey: "DTXcode")
|
||||
@@ -193,7 +204,7 @@ private extension ResignAppOperation
|
||||
if app.isAltStoreApp
|
||||
{
|
||||
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 Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) is String else { throw OperationError.unknownUDID }
|
||||
additionalValues[Bundle.Info.devicePairingString] = "<insert pairing file here>"
|
||||
additionalValues[Bundle.Info.deviceID] = udid
|
||||
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.preferredServerID
|
||||
@@ -237,7 +248,7 @@ private extension ResignAppOperation
|
||||
}
|
||||
|
||||
// Prepare app
|
||||
try prepare(appBundle, additionalInfoDictionaryValues: additionalValues)
|
||||
try prepare(appBundle, bundleID: bundleIdentifier, additionalInfoDictionaryValues: additionalValues)
|
||||
try self.removeMissingAppExtensionReferences(from: appBundle)
|
||||
|
||||
if let directory = appBundle.builtInPlugInsURL, let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants])
|
||||
@@ -254,7 +265,8 @@ private extension ResignAppOperation
|
||||
#endif
|
||||
|
||||
guard let appExtension = Bundle(url: fileURL) else { throw ALTError(.missingAppBundle) }
|
||||
try prepare(appExtension)
|
||||
let updatedAppExBundleId = appExtension.bundleIdentifier?.replacingOccurrences(of: app.bundleIdentifier, with: bundleIdentifier)
|
||||
try prepare(appExtension, bundleID: updatedAppExBundleId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,48 +27,39 @@ final class SendAppOperation: ResultOperation<()>
|
||||
self.progress.totalUnitCount = 1
|
||||
}
|
||||
|
||||
override func main() {
|
||||
override func main()
|
||||
{
|
||||
super.main()
|
||||
|
||||
if let error = self.context.error {
|
||||
|
||||
if let error = self.context.error
|
||||
{
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
|
||||
|
||||
guard let resignedApp = self.context.resignedApp else {
|
||||
return self.finish(.failure(OperationError.invalidParameters("SendAppOperation.main: self.resignedApp is nil")))
|
||||
}
|
||||
|
||||
let shortcutURLoff = URL(string: "shortcuts://run-shortcut?name=TurnOffData")!
|
||||
|
||||
|
||||
// self.context.resignedApp.fileURL points to the app bundle, but we want the .ipa.
|
||||
let app = AnyApp(name: resignedApp.name, bundleIdentifier: self.context.bundleIdentifier, url: resignedApp.fileURL, storeApp: nil)
|
||||
let fileURL = InstalledApp.refreshedIPAURL(for: app)
|
||||
|
||||
print("AFC App `fileURL`: \(fileURL.absoluteString)")
|
||||
|
||||
// Wait for Shortcut to Finish Before Proceeding
|
||||
UIApplication.shared.open(shortcutURLoff, options: [:]) { _ in
|
||||
print("Shortcut finished execution. Proceeding with file transfer.")
|
||||
|
||||
DispatchQueue.global().async {
|
||||
self.processFile(at: fileURL, for: app.bundleIdentifier)
|
||||
|
||||
if let data = NSData(contentsOf: fileURL) {
|
||||
do {
|
||||
let bytes = Data(data).toRustByteSlice()
|
||||
try yeet_app_afc(app.bundleIdentifier, bytes.forRust())
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
} catch {
|
||||
self.finish(.failure(MinimuxerError.RwAfc))
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func processFile(at fileURL: URL, for bundleIdentifier: String) {
|
||||
guard let data = NSData(contentsOf: fileURL) else {
|
||||
} else {
|
||||
print("IPA doesn't exist????")
|
||||
return self.finish(.failure(OperationError(.appNotFound(name: bundleIdentifier))))
|
||||
}
|
||||
|
||||
do {
|
||||
let bytes = Data(data).toRustByteSlice()
|
||||
try yeet_app_afc(bundleIdentifier, bytes.forRust())
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
} catch {
|
||||
self.finish(.failure(MinimuxerError.RwAfc))
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
self.finish(.failure(OperationError(.appNotFound(name: resignedApp.name))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,11 +38,13 @@ final class VerifyAppOperation: ResultOperation<Void>
|
||||
{
|
||||
let permissionsMode: PermissionReviewMode
|
||||
let context: InstallAppOperationContext
|
||||
var customBundleId: String?
|
||||
|
||||
init(permissionsMode: PermissionReviewMode, context: InstallAppOperationContext)
|
||||
init(permissionsMode: PermissionReviewMode, context: InstallAppOperationContext, customBundleId: String? = nil)
|
||||
{
|
||||
self.permissionsMode = permissionsMode
|
||||
self.context = context
|
||||
self.customBundleId = customBundleId
|
||||
|
||||
super.init()
|
||||
}
|
||||
@@ -65,7 +67,8 @@ final class VerifyAppOperation: ResultOperation<Void>
|
||||
}
|
||||
|
||||
if !["ny.litritt.ignited", "com.litritt.ignited"].contains(where: { $0 == app.bundleIdentifier }) {
|
||||
guard app.bundleIdentifier == self.context.bundleIdentifier else {
|
||||
let bundleId = customBundleId ?? app.bundleIdentifier
|
||||
guard bundleId == self.context.bundleIdentifier else {
|
||||
throw VerificationError.mismatchedBundleIdentifiers(sourceBundleID: self.context.bundleIdentifier, app: app)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
//
|
||||
// VerifyAppPledgeOperation.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 12/6/23.
|
||||
// Copyright © 2023 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Combine
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
class VerifyAppPledgeOperation: ResultOperation<Void>
|
||||
{
|
||||
@AsyncManaged
|
||||
private(set) var storeApp: StoreApp
|
||||
|
||||
private let presentingViewController: UIViewController?
|
||||
private var openPatreonPageContinuation: CheckedContinuation<Void, Never>?
|
||||
|
||||
private var cancellable: AnyCancellable?
|
||||
|
||||
init(storeApp: StoreApp, presentingViewController: UIViewController?)
|
||||
{
|
||||
self.storeApp = storeApp
|
||||
self.presentingViewController = presentingViewController
|
||||
}
|
||||
|
||||
override func main()
|
||||
{
|
||||
super.main()
|
||||
|
||||
// _Don't_ rethrow earlier errors, or else user will only be taken to Patreon post if connected to same Wi-Fi as AltServer.
|
||||
// if let error = self.context.error
|
||||
// {
|
||||
// self.finish(.failure(error))
|
||||
// return
|
||||
// }
|
||||
|
||||
Task<Void, Never>.detached(priority: .medium) {
|
||||
do
|
||||
{
|
||||
guard await self.$storeApp.isPledgeRequired else { return self.finish(.success(())) }
|
||||
|
||||
if let presentingViewController = self.presentingViewController
|
||||
{
|
||||
// Ask user to connect Patreon account if they are signed-in to Patreon inside WebViewController, but haven't yet signed in through AltStore settings.
|
||||
// This is most likely because the user joined a Patreon campaign directly through WebViewController before connecting Patreon account in settings.
|
||||
try await self.connectPatreonAccountIfNeeded(presentingViewController: presentingViewController)
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
try await self.verifyPledge()
|
||||
}
|
||||
catch let error as OperationError where error.code == .pledgeRequired || error.code == .pledgeInactive
|
||||
{
|
||||
guard
|
||||
let presentingViewController = self.presentingViewController,
|
||||
let source = await self.$storeApp.source,
|
||||
let patreonURL = await self.$storeApp.perform({ _ in source.patreonURL })
|
||||
else { throw error }
|
||||
|
||||
let components = URLComponents(url: patreonURL, resolvingAgainstBaseURL: false)
|
||||
let lastPathComponent = components?.path.components(separatedBy: "/").last
|
||||
|
||||
let username = lastPathComponent ?? patreonURL.lastPathComponent
|
||||
|
||||
let checkoutURL: URL
|
||||
if await self.$storeApp.prefersCustomPledge, let customPledgeURL = URL(string: "https://www.patreon.com/checkout/" + username + "?rid=0&custom=1")
|
||||
{
|
||||
checkoutURL = customPledgeURL
|
||||
|
||||
let action = await UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default)
|
||||
try await presentingViewController.presentConfirmationAlert(title: NSLocalizedString("Custom Pledge", comment: ""),
|
||||
message: NSLocalizedString("This app supports custom pledges. Pledge any amount on Patreon to receive access.", comment: ""),
|
||||
primaryAction: action)
|
||||
}
|
||||
else if !username.isEmpty, let url = URL(string: "https://www.patreon.com/join/" + username)
|
||||
{
|
||||
// Prefer /join URL over campaign homepage.
|
||||
// URL format from https://support.patreon.com/hc/en-us/articles/360044376211-Managing-members-with-custom-pledges
|
||||
checkoutURL = url
|
||||
}
|
||||
else
|
||||
{
|
||||
checkoutURL = patreonURL
|
||||
}
|
||||
|
||||
// Direct user to Patreon page if they're not already pledged.
|
||||
await self.openPatreonPage(checkoutURL, presentingViewController: presentingViewController)
|
||||
|
||||
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||
if let patreonAccount = await context.performAsync({ DatabaseManager.shared.patreonAccount(in: context) })
|
||||
{
|
||||
// Patreon account is connected, so we'll update it via API to see if pledges changed.
|
||||
// If so, we'll re-fetch the source to update pledge statuses.
|
||||
try await self.updatePledges(for: source, account: patreonAccount)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Patreon account is not connected, so prompt user to connect it.
|
||||
try await self.connectPatreonAccountIfNeeded(presentingViewController: presentingViewController)
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
try await self.verifyPledge()
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore error, but cancel remainder of operation.
|
||||
throw CancellationError()
|
||||
}
|
||||
}
|
||||
|
||||
self.finish(.success(()))
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension VerifyAppPledgeOperation
|
||||
{
|
||||
func verifyPledge() async throws
|
||||
{
|
||||
let (appName, isPledged) = await self.$storeApp.perform { ($0.name, $0.isPledged) }
|
||||
|
||||
if !PatreonAPI.shared.isAuthenticated || !isPledged
|
||||
{
|
||||
let isInstalled = await self.$storeApp.installedApp != nil
|
||||
if isInstalled
|
||||
{
|
||||
// Assume if there is an InstalledApp, the user had previously pledged to this app.
|
||||
throw OperationError.pledgeInactive(appName: appName)
|
||||
}
|
||||
else
|
||||
{
|
||||
throw OperationError.pledgeRequired(appName: appName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func connectPatreonAccountIfNeeded(presentingViewController: UIViewController) async throws
|
||||
{
|
||||
guard !PatreonAPI.shared.isAuthenticated, let authCookie = PatreonAPI.shared.authCookies.first(where: { $0.name.lowercased() == "session_id" }) else { return }
|
||||
|
||||
Logger.sideload.debug("Patreon Auth cookie: \(authCookie.name)=\(authCookie.value)")
|
||||
|
||||
let message = NSLocalizedString("You're signed into Patreon but haven't connected your account with SideStore.\n\nPlease connect your account to download Patreon-exclusive apps.", comment: "")
|
||||
let action = await UIAlertAction(title: NSLocalizedString("Connect Patreon Account", comment: ""), style: .default)
|
||||
|
||||
do
|
||||
{
|
||||
_ = try await presentingViewController.presentConfirmationAlert(title: NSLocalizedString("Patreon Account Detected", comment: ""),
|
||||
message: message, actions: [action])
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore and continue
|
||||
return
|
||||
}
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
PatreonAPI.shared.authenticate(presentingViewController: presentingViewController) { result in
|
||||
do
|
||||
{
|
||||
let account = try result.get()
|
||||
try account.managedObjectContext?.save()
|
||||
|
||||
continuation.resume()
|
||||
}
|
||||
catch
|
||||
{
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let source = await self.$storeApp.source
|
||||
{
|
||||
// Fetch source to update pledge status now that account is connected.
|
||||
try await self.update(source)
|
||||
}
|
||||
}
|
||||
|
||||
func updatePledges(@AsyncManaged for source: Source, @AsyncManaged account: PatreonAccount) async throws
|
||||
{
|
||||
guard PatreonAPI.shared.isAuthenticated else { return }
|
||||
|
||||
let previousPledgeIDs = Set(await $account.perform { $0.pledges.map(\.identifier) })
|
||||
|
||||
let updatedPledgeIDs = try await withCheckedThrowingContinuation { continuation in
|
||||
PatreonAPI.shared.fetchAccount { (result: Result<PatreonAccount, Swift.Error>) in
|
||||
do
|
||||
{
|
||||
let account = try result.get()
|
||||
let pledgeIDs = Set(account.pledges.map(\.identifier))
|
||||
|
||||
try account.managedObjectContext?.save()
|
||||
|
||||
continuation.resume(returning: pledgeIDs)
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.sideload.error("Failed to update Patreon account. \(error.localizedDescription, privacy: .public)")
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if updatedPledgeIDs != previousPledgeIDs
|
||||
{
|
||||
// Active pledges changed, so fetch source to update pledge status.
|
||||
try await self.update(source)
|
||||
}
|
||||
}
|
||||
|
||||
func update(@AsyncManaged _ source: Source) async throws
|
||||
{
|
||||
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||
_ = try await AppManager.shared.fetchSource(sourceURL: $source.sourceURL, managedObjectContext: context)
|
||||
|
||||
try await context.performAsync {
|
||||
try context.save()
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func openPatreonPage(_ patreonURL: URL, presentingViewController: UIViewController) async
|
||||
{
|
||||
let webViewController = WebViewController(url: patreonURL)
|
||||
webViewController.delegate = self
|
||||
|
||||
let navigationController = UINavigationController(rootViewController: webViewController)
|
||||
presentingViewController.present(navigationController, animated: true)
|
||||
|
||||
// Automatically dismiss if user completes checkout flow.
|
||||
self.cancellable = webViewController.webView.publisher(for: \.url, options: [.new])
|
||||
.compactMap { $0 }
|
||||
.compactMap { URLComponents(url: $0, resolvingAgainstBaseURL: false) }
|
||||
.compactMap { components in
|
||||
let lastPathComponent = components.path.components(separatedBy: "/").last
|
||||
return lastPathComponent?.lowercased()
|
||||
}
|
||||
.filter { $0 == "membership" }
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] url in
|
||||
guard let continuation = self?.openPatreonPageContinuation else { return }
|
||||
self?.openPatreonPageContinuation = nil
|
||||
|
||||
continuation.resume()
|
||||
}
|
||||
|
||||
await withCheckedContinuation { continuation in
|
||||
self.openPatreonPageContinuation = continuation
|
||||
}
|
||||
|
||||
// Cache auth cookies just in case user signed in.
|
||||
await PatreonAPI.shared.saveAuthCookies()
|
||||
|
||||
navigationController.dismiss(animated: true)
|
||||
|
||||
self.cancellable = nil
|
||||
}
|
||||
}
|
||||
|
||||
extension VerifyAppPledgeOperation: WebViewControllerDelegate
|
||||
{
|
||||
func webViewControllerDidFinish(_ webViewController: WebViewController)
|
||||
{
|
||||
guard let continuation = self.openPatreonPageContinuation else { return }
|
||||
self.openPatreonPageContinuation = nil
|
||||
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@
|
||||
<key>name</key>
|
||||
<string>Original</string>
|
||||
<key>imageName</key>
|
||||
<string>App</string>
|
||||
<key>iconName</key>
|
||||
<string>AppIcon</string>
|
||||
</dict>
|
||||
</array>
|
||||
@@ -18,66 +20,88 @@
|
||||
<string>Blue</string>
|
||||
<key>imageName</key>
|
||||
<string>Blue</string>
|
||||
<key>iconName</key>
|
||||
<string>BlueIcon</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Dark</string>
|
||||
<key>imageName</key>
|
||||
<string>Dark</string>
|
||||
<key>iconName</key>
|
||||
<string>DarkIcon</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Honeydew</string>
|
||||
<key>imageName</key>
|
||||
<string>Honeydew</string>
|
||||
<key>iconName</key>
|
||||
<string>HoneydewIcon</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Pride</string>
|
||||
<key>imageName</key>
|
||||
<string>Pride</string>
|
||||
<key>iconName</key>
|
||||
<string>PrideIcon</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Sandy</string>
|
||||
<key>imageName</key>
|
||||
<string>Sandy</string>
|
||||
<key>iconName</key>
|
||||
<string>SandyIcon</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Sky</string>
|
||||
<key>imageName</key>
|
||||
<string>Sky</string>
|
||||
<key>iconName</key>
|
||||
<string>SkyIcon</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Snow</string>
|
||||
<key>imageName</key>
|
||||
<string>Snow</string>
|
||||
<key>iconName</key>
|
||||
<string>SnowIcon</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Starburst</string>
|
||||
<key>imageName</key>
|
||||
<string>Starburst</string>
|
||||
<key>iconName</key>
|
||||
<string>StarburstIcon</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Storm</string>
|
||||
<key>imageName</key>
|
||||
<string>Storm</string>
|
||||
<key>iconName</key>
|
||||
<string>StormIcon</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Vista</string>
|
||||
<key>imageName</key>
|
||||
<string>Vista</string>
|
||||
<key>iconName</key>
|
||||
<string>VistaIcon</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Winter</string>
|
||||
<key>imageName</key>
|
||||
<string>Winter</string>
|
||||
<key>iconName</key>
|
||||
<string>WinterIcon</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
|
||||
BIN
AltStore/Resources/Icons.xcassets/Classic/App.imageset/App.png
vendored
Normal file
|
After Width: | Height: | Size: 50 KiB |
12
AltStore/Resources/Icons.xcassets/Classic/App.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "App.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
6
AltStore/Resources/Icons.xcassets/Classic/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
AltStore/Resources/Icons.xcassets/Modern/Blue.imageset/Blue.png
vendored
Normal file
|
After Width: | Height: | Size: 38 KiB |
12
AltStore/Resources/Icons.xcassets/Modern/Blue.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Blue.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 313 KiB After Width: | Height: | Size: 313 KiB |
12
AltStore/Resources/Icons.xcassets/Modern/Dark.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Dark.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
AltStore/Resources/Icons.xcassets/Modern/Dark.imageset/Dark.png
vendored
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
12
AltStore/Resources/Icons.xcassets/Modern/Honeydew.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Honeydew.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
AltStore/Resources/Icons.xcassets/Modern/Honeydew.imageset/Honeydew.png
vendored
Normal file
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 373 KiB After Width: | Height: | Size: 373 KiB |
12
AltStore/Resources/Icons.xcassets/Modern/Pride.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Pride.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
AltStore/Resources/Icons.xcassets/Modern/Pride.imageset/Pride.png
vendored
Normal file
|
After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 719 KiB After Width: | Height: | Size: 719 KiB |
12
AltStore/Resources/Icons.xcassets/Modern/Sandy.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Sandy.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
AltStore/Resources/Icons.xcassets/Modern/Sandy.imageset/Sandy.png
vendored
Normal file
|
After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 352 KiB After Width: | Height: | Size: 352 KiB |
12
AltStore/Resources/Icons.xcassets/Modern/Sky.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Sky.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
AltStore/Resources/Icons.xcassets/Modern/Sky.imageset/Sky.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 135 KiB |
12
AltStore/Resources/Icons.xcassets/Modern/Snow.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Snow.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
AltStore/Resources/Icons.xcassets/Modern/Snow.imageset/Snow.png
vendored
Normal file
|
After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 453 KiB After Width: | Height: | Size: 453 KiB |
12
AltStore/Resources/Icons.xcassets/Modern/Starburst.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Starburst.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
AltStore/Resources/Icons.xcassets/Modern/Starburst.imageset/Starburst.png
vendored
Normal file
|
After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 181 KiB |
12
AltStore/Resources/Icons.xcassets/Modern/Storm.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Storm.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
AltStore/Resources/Icons.xcassets/Modern/Storm.imageset/Storm.png
vendored
Normal file
|
After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 464 KiB After Width: | Height: | Size: 464 KiB |
12
AltStore/Resources/Icons.xcassets/Modern/Vista.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Vista.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
AltStore/Resources/Icons.xcassets/Modern/Vista.imageset/Vista.png
vendored
Normal file
|
After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
12
AltStore/Resources/Icons.xcassets/Modern/Winter.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Winter.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
AltStore/Resources/Icons.xcassets/Modern/Winter.imageset/Winter.png
vendored
Normal file
|
After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 165 KiB |
@@ -40,9 +40,9 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate
|
||||
guard DatabaseManager.shared.isStarted else { return }
|
||||
|
||||
AppManager.shared.update()
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
|
||||
PatreonAPI.shared.refreshPatreonAccount()
|
||||
if UserDefaults.standard.enableEMPforWireguard {
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
}
|
||||
}
|
||||
|
||||
func sceneDidEnterBackground(_ scene: UIScene)
|
||||
@@ -56,7 +56,9 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate
|
||||
// Make sure to update AppDelegate.applicationDidEnterBackground() as well.
|
||||
|
||||
// TODO: @mahee96: find if we need to stop em_proxy as in altstore?
|
||||
// stop_em_proxy()
|
||||
if UserDefaults.standard.enableEMPforWireguard {
|
||||
stop_em_proxy()
|
||||
}
|
||||
|
||||
guard let oneMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date()) else { return }
|
||||
|
||||
@@ -142,8 +144,61 @@ private extension SceneDelegate
|
||||
NotificationCenter.default.post(name: AppDelegate.addSourceDeepLinkNotification, object: nil, userInfo: [AppDelegate.addSourceDeepLinkURLKey: sourceURL])
|
||||
}
|
||||
|
||||
case "pairing":
|
||||
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
|
||||
Logger.main.info("queryItems \(queryItems)")
|
||||
guard let callbackTemplate = queryItems["urlname"]?.removingPercentEncoding else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
exportPairingFile(callbackTemplate)
|
||||
}
|
||||
|
||||
case "certificate":
|
||||
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
|
||||
guard let callbackTemplate = queryItems["callback_template"]?.removingPercentEncoding else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: AppDelegate.exportCertificateNotification, object: nil, userInfo: [AppDelegate.exportCertificateCallbackTemplateKey: callbackTemplate])
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func exportPairingFile(_ urlname: String) {
|
||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let window = windowScene.windows.first, let viewcontroller = window.rootViewController {
|
||||
let fm = FileManager.default
|
||||
let documentsPath = fm.documentsDirectory.appendingPathComponent("ALTPairingFile.mobiledevicepairing")
|
||||
|
||||
|
||||
guard let data = try? Data(contentsOf: documentsPath) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to find Pairing File!", comment: ""), detailText: nil)
|
||||
toastView.show(in: viewcontroller)
|
||||
return
|
||||
}
|
||||
|
||||
let base64encodedCert = data.base64EncodedString()
|
||||
var allowedQueryParamAndKey = NSCharacterSet.urlQueryAllowed
|
||||
allowedQueryParamAndKey.remove(charactersIn: ";/?:@&=+$, ")
|
||||
guard let encodedCert = base64encodedCert.addingPercentEncoding(withAllowedCharacters: allowedQueryParamAndKey) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to encode pairingFile!", comment: ""), detailText: nil)
|
||||
toastView.show(in: viewcontroller)
|
||||
return
|
||||
}
|
||||
|
||||
var urlStr = "\(urlname)://pairingFile?data=$(BASE64_PAIRING)"
|
||||
let finished = urlStr.replacingOccurrences(of: "$(BASE64_PAIRING)", with: encodedCert, options: .literal, range: nil)
|
||||
|
||||
print(finished)
|
||||
guard let callbackUrl = URL(string: finished) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to initialize callback URL!", comment: ""), detailText: nil)
|
||||
toastView.show(in: viewcontroller)
|
||||
return
|
||||
}
|
||||
UIApplication.shared.open(callbackUrl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="24506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24504"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -23,7 +23,7 @@
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="f7H-EV-7Sx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="55"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="SideStore" translatesAutoresizingMaskIntoConstraints="NO" id="pn6-Ic-MJm">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="SideStore" translatesAutoresizingMaskIntoConstraints="NO" id="pn6-Ic-MJm" userLabel="Icon View">
|
||||
<rect key="frame" x="0.0" y="0.0" width="55" height="55"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="55" id="7LH-UB-M1b"/>
|
||||
@@ -31,36 +31,19 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="si2-MA-3RH">
|
||||
<rect key="frame" x="65" y="0.0" width="213" height="55"/>
|
||||
<rect key="frame" x="65" y="0.0" width="278" height="55"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="hkS-oz-wiT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="100.5" height="55"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="278" height="55"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideTeam" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="DTd-Yu-HXr">
|
||||
<rect key="frame" x="0.0" y="0.0" width="100.5" height="21.5"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideTeam" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="DTd-Yu-HXr" userLabel="Team name">
|
||||
<rect key="frame" x="0.0" y="0.0" width="278" height="21.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="18"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nPA-DN-RTG" userLabel=" ">
|
||||
<rect key="frame" x="0.0" y="23.5" width="100.5" height="31.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" white="1" alpha="0.65000000000000002" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="TFB-qo-cbh">
|
||||
<rect key="frame" x="127" y="0.0" width="86" height="55"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="Zpb-k3-y7l">
|
||||
<rect key="frame" x="0.0" y="0.0" width="86" height="50"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="18"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VOu-b8-uEL">
|
||||
<rect key="frame" x="0.0" y="52" width="86" height="3"/>
|
||||
<rect key="frame" x="0.0" y="23.5" width="278" height="31.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" white="1" alpha="0.65000000000000002" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -69,14 +52,6 @@
|
||||
</stackView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Shane" translatesAutoresizingMaskIntoConstraints="NO" id="F6g-4g-Gr2">
|
||||
<rect key="frame" x="288" y="0.0" width="55" height="55"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="55" id="CaK-rR-Zjy"/>
|
||||
<constraint firstAttribute="width" secondItem="F6g-4g-Gr2" secondAttribute="height" id="cCw-He-Yyc"/>
|
||||
<constraint firstAttribute="width" secondItem="F6g-4g-Gr2" secondAttribute="height" id="geK-Xu-ybL"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="55" id="Uiy-9X-WSO"/>
|
||||
@@ -149,7 +124,6 @@ Following us on social media allows us to give quick updates and spread the word
|
||||
<outlet property="instagramButton" destination="VdY-7Q-amF" id="5kj-9x-k4F"/>
|
||||
<outlet property="rileyImageView" destination="pn6-Ic-MJm" id="60i-Q0-ojz"/>
|
||||
<outlet property="rileyLabel" destination="DTd-Yu-HXr" id="O0y-JB-gWp"/>
|
||||
<outlet property="shaneLabel" destination="Zpb-k3-y7l" id="aQN-6B-s5T"/>
|
||||
<outlet property="supportButton" destination="yEi-L6-kQ8" id="Dzo-vd-SnD"/>
|
||||
<outlet property="textView" destination="FeG-e5-LJl" id="K0M-lF-I6u"/>
|
||||
<outlet property="twitterButton" destination="hov-Ce-LaM" id="gib-Lt-qtY"/>
|
||||
@@ -158,8 +132,7 @@ Following us on social media allows us to give quick updates and spread the word
|
||||
</collectionReusableView>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="Shane" width="128" height="128"/>
|
||||
<image name="SideStore" width="1024" height="1024"/>
|
||||
<image name="SideStore" width="512" height="512"/>
|
||||
<namedColor name="SettingsHighlighted">
|
||||
<color red="0.38823529411764707" green="0.011764705882352941" blue="0.58823529411764708" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
|
||||
@@ -20,15 +20,17 @@ extension UIApplication
|
||||
|
||||
private final class AltIcon: Decodable
|
||||
{
|
||||
static let defaultIconName: String = "AppIcon"
|
||||
static let defaultName: String = "Original"
|
||||
|
||||
var name: String
|
||||
var imageName: String
|
||||
var iconName: String
|
||||
|
||||
private enum CodingKeys: String, CodingKey
|
||||
{
|
||||
case name
|
||||
case imageName
|
||||
case iconName
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws
|
||||
@@ -36,6 +38,7 @@ private final class AltIcon: Decodable
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.name = try container.decode(String.self, forKey: .name)
|
||||
self.imageName = try container.decode(String.self, forKey: .imageName)
|
||||
self.iconName = try container.decode(String.self, forKey: .iconName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,20 +149,14 @@ private extension AltAppIconsViewController
|
||||
config.textProperties.font = font
|
||||
config.textProperties.color = .label
|
||||
|
||||
// we have to do this hardcodded name hack for .appiconset
|
||||
// else one can supply the artifacts via .imageset
|
||||
let image: UIImage? =
|
||||
UIImage(named: "\(icon.imageName)76x76@2x~ipad") ??
|
||||
UIImage(named: "\(icon.imageName)60x60@2x") ??
|
||||
UIImage(named: icon.imageName)
|
||||
|
||||
let image = UIImage(named: icon.imageName)
|
||||
config.image = image
|
||||
config.imageProperties.maximumSize = CGSize(width: imageWidth, height: imageWidth)
|
||||
config.imageProperties.cornerRadius = imageWidth / 5.0 // Copied from AppIconImageView
|
||||
|
||||
cell.contentConfiguration = config
|
||||
|
||||
if UIApplication.shared.alternateIconName == icon.imageName || (UIApplication.shared.alternateIconName == nil && icon.imageName == AltIcon.defaultIconName)
|
||||
if UIApplication.shared.alternateIconName == icon.iconName || (UIApplication.shared.alternateIconName == nil && icon.name == AltIcon.defaultName)
|
||||
{
|
||||
cell.accessories = [.checkmark(options: .init(tintColor: .white))]
|
||||
}
|
||||
@@ -167,7 +164,7 @@ private extension AltAppIconsViewController
|
||||
{
|
||||
cell.accessories = []
|
||||
}
|
||||
|
||||
|
||||
var backgroundConfiguration = UIBackgroundConfiguration.listPlainCell()
|
||||
backgroundConfiguration.backgroundColorTransformer = UIConfigurationColorTransformer { [weak cell] c in
|
||||
if let state = cell?.configurationState, state.isHighlighted
|
||||
@@ -199,14 +196,14 @@ extension AltAppIconsViewController
|
||||
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
|
||||
{
|
||||
let icon = self.dataSource.item(at: indexPath)
|
||||
guard UIApplication.shared.alternateIconName != icon.imageName else { return }
|
||||
guard UIApplication.shared.alternateIconName != icon.iconName else { return }
|
||||
|
||||
// Deselect previous icon + select new icon
|
||||
collectionView.reloadData()
|
||||
|
||||
// If assigning primary icon, pass "nil" as alternate icon name.
|
||||
let imageName = (icon.imageName == "AppIcon") ? nil : icon.imageName
|
||||
UIApplication.shared.setAlternateIconName(imageName) { error in
|
||||
let iconName = (icon.name == AltIcon.defaultName) ? nil : icon.iconName
|
||||
UIApplication.shared.setAlternateIconName(iconName) { error in
|
||||
if let error
|
||||
{
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Unable to Change App Icon", comment: ""),
|
||||
|
||||
@@ -380,13 +380,8 @@ private extension ErrorLogViewController
|
||||
|
||||
func searchFAQ(for loggedError: LoggedError)
|
||||
{
|
||||
let baseURL = URL(string: "https://faq.altstore.io/getting-started/error-codes")!
|
||||
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)!
|
||||
|
||||
let query = [loggedError.domain, "\(loggedError.error.displayCode)"].joined(separator: "+")
|
||||
components.queryItems = [URLQueryItem(name: "q", value: query)]
|
||||
|
||||
let safariViewController = SFSafariViewController(url: components.url ?? baseURL)
|
||||
let staticURL = URL(string: "https://docs.sidestore.io/docs/troubleshooting/error-codes")!
|
||||
let safariViewController = SFSafariViewController(url: staticURL)
|
||||
safariViewController.preferredControlTintColor = .altPrimary
|
||||
self.present(safariViewController, animated: true)
|
||||
}
|
||||
|
||||
@@ -42,63 +42,56 @@ struct OperationsLoggingControlView: View {
|
||||
}
|
||||
))
|
||||
|
||||
CustomToggle("2. VerifyAppPledge", isOn: Binding(
|
||||
get: { self.viewModel.getFromDatabase(for: VerifyAppPledgeOperation.self) },
|
||||
set: { value in
|
||||
self.viewModel.updateDatabase(for: VerifyAppPledgeOperation.self, value: value)
|
||||
}
|
||||
))
|
||||
|
||||
CustomToggle("3. DownloadApp", isOn: Binding(
|
||||
CustomToggle("2. DownloadApp", isOn: Binding(
|
||||
get: { self.viewModel.getFromDatabase(for: DownloadAppOperation.self) },
|
||||
set: { value in
|
||||
self.viewModel.updateDatabase(for: DownloadAppOperation.self, value: value)
|
||||
}
|
||||
))
|
||||
|
||||
CustomToggle("4. VerifyApp", isOn: Binding(
|
||||
CustomToggle("3. VerifyApp", isOn: Binding(
|
||||
get: { self.viewModel.getFromDatabase(for: VerifyAppOperation.self) },
|
||||
set: { value in
|
||||
self.viewModel.updateDatabase(for: VerifyAppOperation.self, value: value)
|
||||
}
|
||||
))
|
||||
|
||||
CustomToggle("5. RemoveAppExtensions", isOn: Binding(
|
||||
CustomToggle("4. RemoveAppExtensions", isOn: Binding(
|
||||
get: { self.viewModel.getFromDatabase(for: RemoveAppExtensionsOperation.self) },
|
||||
set: { value in
|
||||
self.viewModel.updateDatabase(for: RemoveAppExtensionsOperation.self, value: value)
|
||||
}
|
||||
))
|
||||
|
||||
CustomToggle("6. FetchAnisetteData", isOn: Binding(
|
||||
CustomToggle("5. FetchAnisetteData", isOn: Binding(
|
||||
get: { self.viewModel.getFromDatabase(for: FetchAnisetteDataOperation.self) },
|
||||
set: { value in
|
||||
self.viewModel.updateDatabase(for: FetchAnisetteDataOperation.self, value: value)
|
||||
}
|
||||
))
|
||||
|
||||
CustomToggle("7. FetchProvisioningProfiles(I)", isOn: Binding(
|
||||
CustomToggle("6. FetchProvisioningProfiles(I)", isOn: Binding(
|
||||
get: { self.viewModel.getFromDatabase(for: FetchProvisioningProfilesInstallOperation.self) },
|
||||
set: { value in
|
||||
self.viewModel.updateDatabase(for: FetchProvisioningProfilesInstallOperation.self, value: value)
|
||||
}
|
||||
))
|
||||
|
||||
CustomToggle("8. ResignApp", isOn: Binding(
|
||||
CustomToggle("7. ResignApp", isOn: Binding(
|
||||
get: { self.viewModel.getFromDatabase(for: ResignAppOperation.self) },
|
||||
set: { value in
|
||||
self.viewModel.updateDatabase(for: ResignAppOperation.self, value: value)
|
||||
}
|
||||
))
|
||||
|
||||
CustomToggle("9. SendApp", isOn: Binding(
|
||||
CustomToggle("8. SendApp", isOn: Binding(
|
||||
get: { self.viewModel.getFromDatabase(for: SendAppOperation.self) },
|
||||
set: { value in
|
||||
self.viewModel.updateDatabase(for: SendAppOperation.self, value: value)
|
||||
}
|
||||
))
|
||||
|
||||
CustomToggle("10. InstallApp", isOn: Binding(
|
||||
CustomToggle("9. InstallApp", isOn: Binding(
|
||||
get: { self.viewModel.getFromDatabase(for: InstallAppOperation.self) },
|
||||
set: { value in
|
||||
self.viewModel.updateDatabase(for: InstallAppOperation.self, value: value)
|
||||
@@ -203,16 +196,6 @@ struct OperationsLoggingControlView: View {
|
||||
))
|
||||
}
|
||||
|
||||
CustomSection(header: Text("Patrons Operations"))
|
||||
{
|
||||
CustomToggle("1. UpdatePatrons", isOn: Binding(
|
||||
get: { self.viewModel.getFromDatabase(for: UpdatePatronsOperation.self) },
|
||||
set: { value in
|
||||
self.viewModel.updateDatabase(for: UpdatePatronsOperation.self, value: value)
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
CustomSection(header: Text("Cache Operations"))
|
||||
{
|
||||
CustomToggle("1. ClearAppCache", isOn: Binding(
|
||||
|
||||
@@ -13,60 +13,18 @@ final class PatronCollectionViewCell: UICollectionViewCell
|
||||
@IBOutlet var textLabel: UILabel!
|
||||
}
|
||||
|
||||
final class PatronsHeaderView: UICollectionReusableView
|
||||
{
|
||||
let textLabel = UILabel()
|
||||
|
||||
override init(frame: CGRect)
|
||||
{
|
||||
super.init(frame: frame)
|
||||
|
||||
self.textLabel.font = UIFont.boldSystemFont(ofSize: 17)
|
||||
self.textLabel.textColor = .white
|
||||
self.addSubview(self.textLabel, pinningEdgesWith: UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20))
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
final class PatronsFooterView: UICollectionReusableView
|
||||
{
|
||||
let button = UIButton(type: .system)
|
||||
|
||||
override init(frame: CGRect)
|
||||
{
|
||||
super.init(frame: frame)
|
||||
|
||||
self.button.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.button.activityIndicatorView.style = .medium
|
||||
self.button.activityIndicatorView.color = .white
|
||||
self.button.titleLabel?.textColor = .white
|
||||
self.addSubview(self.button)
|
||||
|
||||
NSLayoutConstraint.activate([self.button.centerXAnchor.constraint(equalTo: self.centerXAnchor),
|
||||
self.button.centerYAnchor.constraint(equalTo: self.centerYAnchor)])
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
final class AboutPatreonHeaderView: UICollectionReusableView
|
||||
{
|
||||
@IBOutlet var supportButton: UIButton!
|
||||
@IBOutlet var twitterButton: UIButton!
|
||||
@IBOutlet var instagramButton: UIButton!
|
||||
@IBOutlet var accountButton: UIButton!
|
||||
@IBOutlet var textView: UITextView!
|
||||
|
||||
@IBOutlet private var rileyLabel: UILabel!
|
||||
@IBOutlet private var shaneLabel: UILabel!
|
||||
|
||||
@IBOutlet private var rileyImageView: UIImageView!
|
||||
@IBOutlet private var shaneImageView: UIImageView!
|
||||
|
||||
override func awakeFromNib()
|
||||
{
|
||||
@@ -76,13 +34,13 @@ final class AboutPatreonHeaderView: UICollectionReusableView
|
||||
self.textView.layer.cornerRadius = 20
|
||||
self.textView.textContainer.lineFragmentPadding = 0
|
||||
|
||||
for imageView in [self.rileyImageView, self.shaneImageView].compactMap({$0})
|
||||
for imageView in [self.rileyImageView].compactMap({$0})
|
||||
{
|
||||
imageView.clipsToBounds = true
|
||||
imageView.layer.cornerRadius = imageView.bounds.midY
|
||||
}
|
||||
|
||||
for button in [self.supportButton, self.accountButton].compactMap({$0})
|
||||
for button in [self.supportButton, self.twitterButton, self.instagramButton].compactMap({$0})
|
||||
{
|
||||
button.clipsToBounds = true
|
||||
button.layer.cornerRadius = 16
|
||||
|
||||
@@ -13,26 +13,10 @@ import AuthenticationServices
|
||||
import AltStoreCore
|
||||
import Roxas
|
||||
|
||||
extension PatreonViewController
|
||||
{
|
||||
private enum Section: Int, CaseIterable
|
||||
{
|
||||
case about
|
||||
case patrons
|
||||
}
|
||||
}
|
||||
|
||||
final class PatreonViewController: UICollectionViewController
|
||||
{
|
||||
// private lazy var dataSource = self.makeDataSource()
|
||||
private lazy var patronsDataSource = self.makePatronsDataSource()
|
||||
|
||||
private var prototypeAboutHeader: AboutPatreonHeaderView!
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return .lightContent
|
||||
}
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
super.viewDidLoad()
|
||||
@@ -40,24 +24,14 @@ final class PatreonViewController: UICollectionViewController
|
||||
let aboutHeaderNib = UINib(nibName: "AboutPatreonHeaderView", bundle: nil)
|
||||
self.prototypeAboutHeader = aboutHeaderNib.instantiate(withOwner: nil, options: nil)[0] as? AboutPatreonHeaderView
|
||||
|
||||
// self.collectionView.dataSource = self.dataSource
|
||||
|
||||
self.collectionView.register(aboutHeaderNib, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "AboutHeader")
|
||||
self.collectionView.register(PatronsHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "PatronsHeader")
|
||||
self.collectionView.register(PatronsFooterView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "PatronsFooter")
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(PatreonViewController.didUpdatePatrons(_:)), name: AppManager.didUpdatePatronsNotification, object: nil)
|
||||
|
||||
self.update()
|
||||
self.collectionView.reloadData()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool)
|
||||
{
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
//self.fetchPatrons()
|
||||
|
||||
self.update()
|
||||
self.collectionView.reloadData()
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews()
|
||||
@@ -76,103 +50,17 @@ final class PatreonViewController: UICollectionViewController
|
||||
|
||||
private extension PatreonViewController
|
||||
{
|
||||
func makeDataSource() -> RSTCompositeCollectionViewDataSource<ManagedPatron>
|
||||
{
|
||||
let aboutDataSource = RSTDynamicCollectionViewDataSource<ManagedPatron>()
|
||||
aboutDataSource.numberOfSectionsHandler = { 1 }
|
||||
aboutDataSource.numberOfItemsHandler = { _ in 0 }
|
||||
|
||||
let dataSource = RSTCompositeCollectionViewDataSource<ManagedPatron>(dataSources: [aboutDataSource, self.patronsDataSource])
|
||||
dataSource.proxy = self
|
||||
return dataSource
|
||||
}
|
||||
|
||||
func makePatronsDataSource() -> RSTFetchedResultsCollectionViewDataSource<ManagedPatron>
|
||||
{
|
||||
let fetchRequest: NSFetchRequest<ManagedPatron> = ManagedPatron.fetchRequest()
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(ManagedPatron.name), ascending: true, selector: #selector(NSString.caseInsensitiveCompare(_:)))]
|
||||
|
||||
let patronsDataSource = RSTFetchedResultsCollectionViewDataSource<ManagedPatron>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
|
||||
patronsDataSource.cellConfigurationHandler = { (cell, patron, indexPath) in
|
||||
let cell = cell as! PatronCollectionViewCell
|
||||
cell.textLabel.text = patron.name
|
||||
}
|
||||
|
||||
return patronsDataSource
|
||||
}
|
||||
|
||||
func update()
|
||||
{
|
||||
self.collectionView.reloadData()
|
||||
}
|
||||
|
||||
func prepare(_ headerView: AboutPatreonHeaderView)
|
||||
{
|
||||
headerView.layoutMargins = self.view.layoutMargins
|
||||
|
||||
headerView.supportButton.addTarget(self, action: #selector(PatreonViewController.openPatreonURL(_:)), for: .primaryActionTriggered)
|
||||
headerView.twitterButton.addTarget(self, action: #selector(PatreonViewController.openTwitterURL(_:)), for: .primaryActionTriggered)
|
||||
headerView.instagramButton.addTarget(self, action: #selector(PatreonViewController.openInstagramURL(_:)), for: .primaryActionTriggered)
|
||||
|
||||
let defaultSupportButtonTitle = NSLocalizedString("Become a patron", comment: "")
|
||||
let isPatronSupportButtonTitle = NSLocalizedString("View Patreon", comment: "")
|
||||
|
||||
let defaultText = NSLocalizedString("""
|
||||
Hello, thank you for using SideStore!
|
||||
|
||||
If you would subscribe to the patreon that would support us and make sure we can continue developing SideStore for you.
|
||||
|
||||
-SideTeam
|
||||
""", comment: "")
|
||||
|
||||
let isPatronText = NSLocalizedString("""
|
||||
Hey ,
|
||||
|
||||
You’re the best. Your account was linked successfully, so you now have access to any beta versions of our apps. You can find them all in the Browse tab.
|
||||
|
||||
Thanks for all of your support. Enjoy!
|
||||
- SideTeam
|
||||
""", comment: "")
|
||||
|
||||
if let account = DatabaseManager.shared.patreonAccount(), PatreonAPI.shared.isAuthenticated
|
||||
{
|
||||
headerView.accountButton.addTarget(self, action: #selector(PatreonViewController.signOut(_:)), for: .primaryActionTriggered)
|
||||
headerView.accountButton.setTitle(String(format: NSLocalizedString("Unlink %@", comment: ""), account.name), for: .normal)
|
||||
|
||||
if account.isAltStorePatron
|
||||
{
|
||||
headerView.supportButton.setTitle(isPatronSupportButtonTitle, for: .normal)
|
||||
|
||||
let font = UIFont.systemFont(ofSize: 16)
|
||||
|
||||
let attributedText = NSMutableAttributedString(string: isPatronText, attributes: [.font: font,
|
||||
.foregroundColor: UIColor.white])
|
||||
|
||||
let boldedName = NSAttributedString(string: account.firstName ?? account.name,
|
||||
attributes: [.font: UIFont.boldSystemFont(ofSize: font.pointSize),
|
||||
.foregroundColor: UIColor.white])
|
||||
attributedText.insert(boldedName, at: 4)
|
||||
|
||||
headerView.textView.attributedText = attributedText
|
||||
}
|
||||
else
|
||||
{
|
||||
headerView.supportButton.setTitle(defaultSupportButtonTitle, for: .normal)
|
||||
headerView.textView.text = defaultText
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private extension PatreonViewController
|
||||
{
|
||||
@objc func fetchPatrons()
|
||||
{
|
||||
AppManager.shared.updatePatronsIfNeeded()
|
||||
self.update()
|
||||
}
|
||||
|
||||
@objc func openPatreonURL(_ sender: UIButton)
|
||||
{
|
||||
let patreonURL = URL(string: "https://www.patreon.com/SideStoreIO")!
|
||||
@@ -199,148 +87,15 @@ private extension PatreonViewController
|
||||
safariViewController.preferredControlTintColor = self.view.tintColor
|
||||
self.present(safariViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func authenticate(_ sender: UIBarButtonItem)
|
||||
{
|
||||
PatreonAPI.shared.authenticate(presentingViewController: self) { (result) in
|
||||
do
|
||||
{
|
||||
let account = try result.get()
|
||||
try account.managedObjectContext?.save()
|
||||
|
||||
// Update sources to show any Patreon-only apps.
|
||||
AppManager.shared.fetchSources { result in
|
||||
do
|
||||
{
|
||||
let (_, context) = try result.get()
|
||||
try context.save()
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.main.error("Failed to update sources after authenticating Patreon account. \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
catch is CancellationError
|
||||
{
|
||||
// Clear in-app browser cache in case they are signed into wrong account.
|
||||
Task<Void, Never>.detached {
|
||||
await PatreonAPI.shared.deleteAuthCookies()
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func signOut(_ sender: UIBarButtonItem)
|
||||
{
|
||||
func signOut()
|
||||
{
|
||||
PatreonAPI.shared.signOut { (result) in
|
||||
do
|
||||
{
|
||||
try result.get()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if MARKETPLACE
|
||||
let message = NSLocalizedString("You will no longer be able to install or update any apps that require pledges.", comment: "")
|
||||
#else
|
||||
let message = NSLocalizedString("You will no longer be able to install or refresh any apps that require pledges.", comment: "")
|
||||
#endif
|
||||
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to unlink your Patreon account?", comment: ""), message: message, preferredStyle: .actionSheet)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Unlink Patreon Account", comment: ""), style: .destructive) { _ in signOut() })
|
||||
alertController.addAction(.cancel)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc func didUpdatePatrons(_ notification: Notification)
|
||||
{
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
// Wait short delay before reloading or else footer won't properly update if it's already visible 🤷♂️
|
||||
self.collectionView.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PatreonViewController
|
||||
{
|
||||
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
|
||||
{
|
||||
let section = Section.allCases[indexPath.section]
|
||||
switch section
|
||||
{
|
||||
case .about:
|
||||
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "AboutHeader", for: indexPath) as! AboutPatreonHeaderView
|
||||
self.prepare(headerView)
|
||||
return headerView
|
||||
|
||||
case .patrons:
|
||||
if kind == UICollectionView.elementKindSectionHeader
|
||||
{
|
||||
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "PatronsHeader", for: indexPath) as! PatronsHeaderView
|
||||
headerView.textLabel.text = NSLocalizedString("Special thanks to...", comment: "")
|
||||
return headerView
|
||||
}
|
||||
else
|
||||
{
|
||||
let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "PatronsFooter", for: indexPath) as! PatronsFooterView
|
||||
footerView.button.isIndicatingActivity = false
|
||||
footerView.button.isHidden = false
|
||||
//footerView.button.addTarget(self, action: #selector(PatreonViewController.fetchPatrons), for: .primaryActionTriggered)
|
||||
|
||||
switch AppManager.shared.updatePatronsResult
|
||||
{
|
||||
case .none: footerView.button.isIndicatingActivity = true
|
||||
case .success?: footerView.button.isHidden = true
|
||||
case .failure?:
|
||||
// In simulator debug builds only enable debug mode flag
|
||||
#if DEBUG && targetEnvironment(simulator)
|
||||
let debug = true
|
||||
#else
|
||||
let debug = false
|
||||
#endif
|
||||
|
||||
if self.patronsDataSource.itemCount == 0 || debug
|
||||
{
|
||||
// Only show error message if there aren't any cached Patrons (or if this is a debug build).
|
||||
|
||||
footerView.button.isHidden = false
|
||||
footerView.button.setTitle(NSLocalizedString("Error Loading Patrons", comment: ""), for: .normal)
|
||||
}
|
||||
else
|
||||
{
|
||||
footerView.button.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
return footerView
|
||||
}
|
||||
}
|
||||
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "AboutHeader", for: indexPath) as! AboutPatreonHeaderView
|
||||
self.prepare(headerView)
|
||||
return headerView
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,31 +103,11 @@ extension PatreonViewController: UICollectionViewDelegateFlowLayout
|
||||
{
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize
|
||||
{
|
||||
let section = Section.allCases[section]
|
||||
switch section
|
||||
{
|
||||
case .about:
|
||||
let widthConstraint = self.prototypeAboutHeader.widthAnchor.constraint(equalToConstant: collectionView.bounds.width)
|
||||
NSLayoutConstraint.activate([widthConstraint])
|
||||
defer { NSLayoutConstraint.deactivate([widthConstraint]) }
|
||||
|
||||
self.prepare(self.prototypeAboutHeader)
|
||||
|
||||
let size = self.prototypeAboutHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
|
||||
return size
|
||||
|
||||
case .patrons:
|
||||
return CGSize(width: 0, height: 0)
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize
|
||||
{
|
||||
let section = Section.allCases[section]
|
||||
switch section
|
||||
{
|
||||
case .about: return .zero
|
||||
case .patrons: return CGSize(width: 320, height: 44)
|
||||
}
|
||||
let widthConstraint = self.prototypeAboutHeader.widthAnchor.constraint(equalToConstant: collectionView.bounds.width)
|
||||
NSLayoutConstraint.activate([widthConstraint])
|
||||
defer { NSLayoutConstraint.deactivate([widthConstraint]) }
|
||||
|
||||
self.prepare(self.prototypeAboutHeader)
|
||||
return self.prototypeAboutHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="5Rz-4h-jJ8">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="5Rz-4h-jJ8">
|
||||
<device id="retina6_3" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24504"/>
|
||||
<capability name="Image references" minToolsVersion="12.0"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
@@ -22,7 +22,7 @@
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<stackView key="tableFooterView" opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalCentering" alignment="center" spacing="15" id="48g-cT-stR">
|
||||
<rect key="frame" x="0.0" y="1986.3333377838135" width="402" height="125"/>
|
||||
<rect key="frame" x="0.0" y="2442.6666641235352" width="402" height="125"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="900" text="Follow SideStore for updates" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XFa-MY-7cV">
|
||||
@@ -104,7 +104,7 @@
|
||||
<tableViewSection headerTitle="" id="flW-d4-bco">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="7lu-Yk-87t" rowHeight="51" style="IBUITableViewCellStyleDefault" id="DzJ-TL-jvR" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="22.333333969116211" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="17.666666030883789" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="DzJ-TL-jvR" id="XnZ-bO-peM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -134,7 +134,7 @@
|
||||
<tableViewSection headerTitle="" id="CAI-9J-8fR">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="xvY-lN-Toz" detailTextLabel="CnN-M1-AYK" rowHeight="51" style="IBUITableViewCellStyleValue1" id="kCH-yh-bMk" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="113.66666793823242" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="104.33333206176758" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="kCH-yh-bMk" id="MQ9-Qn-bWg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -166,7 +166,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="rAc-lQ-B1k" detailTextLabel="0uP-Cd-tNX" rowHeight="51" style="IBUITableViewCellStyleValue1" id="q11-3k-oIm" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="164.66666793823242" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="155.33333206176758" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="q11-3k-oIm" id="QCY-a8-Lhx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -198,7 +198,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="Sge-cM-Fw9" detailTextLabel="434-MW-Den" rowHeight="51" style="IBUITableViewCellStyleValue1" id="vuc-eX-w3f" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="215.66666793823242" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="206.33333206176758" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="vuc-eX-w3f" id="wpD-YB-mrf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -234,7 +234,7 @@
|
||||
<tableViewSection id="YHi-gR-wed">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="R1C-Gr-xD4" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="302.66666793823242" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="293.33333206176758" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="R1C-Gr-xD4" id="Ojx-7f-z7E">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -275,7 +275,7 @@
|
||||
<tableViewSection id="RpS-Hn-sQU">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="9dU-Hl-NiJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="389.66666793823242" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="380.33333206176758" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="9dU-Hl-NiJ" id="w62-f1-Ody">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -316,7 +316,7 @@
|
||||
<tableViewSection headerTitle="" id="2dM-lg-cRI">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Rra-U5-kCd" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="481.00000190734863" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="466.99999809265137" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Rra-U5-kCd" id="8gV-kx-lGe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -329,7 +329,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DPu-zD-Als">
|
||||
<rect key="frame" x="323" y="10" width="51" height="31"/>
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleIsBackgroundRefreshEnabled:" destination="aMk-Xp-UL8" eventType="valueChanged" id="gK5-Wr-8Hh"/>
|
||||
</connections>
|
||||
@@ -351,7 +351,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<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="532.00000190734863" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="517.99999809265137" width="402" height="51"/>
|
||||
<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="402" height="51"/>
|
||||
@@ -364,7 +364,7 @@
|
||||
<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="323" y="10" width="51" height="31"/>
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleNoIdleTimeoutEnabled:" destination="aMk-Xp-UL8" eventType="valueChanged" id="WSl-Jc-g5J"/>
|
||||
</connections>
|
||||
@@ -386,7 +386,7 @@
|
||||
</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="583.00000190734863" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="568.99999809265137" width="402" 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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -414,7 +414,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="7PQ-AW-GcV" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="634.00000190734863" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="619.99999809265137" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="7PQ-AW-GcV" id="wQ8-9w-iiw">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -427,7 +427,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1aa-og-ZXD">
|
||||
<rect key="frame" x="323" y="10" width="51" height="31"/>
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleDisableAppLimit:" destination="aMk-Xp-UL8" eventType="valueChanged" id="zYc-B2-JPg"/>
|
||||
</connections>
|
||||
@@ -453,7 +453,7 @@
|
||||
<tableViewSection headerTitle="" id="eHy-qI-w5w">
|
||||
<cells>
|
||||
<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="725.33333587646484" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="706.66666412353516" width="402" height="51"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -494,7 +494,7 @@
|
||||
<tableViewSection id="1fc-f1-ALD">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="7Ek-Ls-QVO" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="812.33333587646484" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="793.66666412353516" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="7Ek-Ls-QVO" id="KjD-M3-oNg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -531,7 +531,7 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="hFh-X1-ZAi" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="863.33333587646484" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="844.66666412353516" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="hFh-X1-ZAi" id="nCs-Ro-A6t">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -563,7 +563,7 @@
|
||||
<tableViewSection headerTitle="" id="J90-vn-u2O">
|
||||
<cells>
|
||||
<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="954.66666984558105" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="931.33333015441895" width="402" height="51"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -608,7 +608,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<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="1005.6666698455811" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="982.33333015441895" width="402" height="51"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -653,7 +653,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<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="1056.6666698455811" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1033.3333301544189" width="402" height="51"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -675,7 +675,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY">
|
||||
<rect key="frame" x="121" y="-1" width="15.666666666666629" height="22.333333333333332"/>
|
||||
<rect key="frame" x="120.99999999999997" y="-1" width="15.666666666666657" height="22.333333333333332"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -698,7 +698,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<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="1071.6666698455811" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1084.3333301544189" width="402" height="51"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -736,22 +736,98 @@
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="" id="swj-Wc-IR6">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="xGq-wV-SCd" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1170.9999961853027" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="xGq-wV-SCd" id="G7G-sK-oO3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Enable Beta Updates" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1B5-BJ-Rkb">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="169" height="20.333333333333329"/>
|
||||
<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" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Blb-Dp-9QF">
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleEnableBetaUpdates:" destination="aMk-Xp-UL8" eventType="valueChanged" id="9Ea-BQ-DAE"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="1B5-BJ-Rkb" firstAttribute="leading" secondItem="G7G-sK-oO3" secondAttribute="leadingMargin" id="9Yc-mg-fVt"/>
|
||||
<constraint firstItem="Blb-Dp-9QF" firstAttribute="centerY" secondItem="G7G-sK-oO3" secondAttribute="centerY" id="U0M-9E-rKO"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="Blb-Dp-9QF" secondAttribute="trailing" id="mhe-jJ-7UR"/>
|
||||
<constraint firstItem="1B5-BJ-Rkb" firstAttribute="centerY" secondItem="G7G-sK-oO3" secondAttribute="centerY" id="s2a-rw-jgc"/>
|
||||
</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="1"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="eHa-Cd-p4h" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1221.9999961853027" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="eHa-Cd-p4h" id="V9s-7b-vkR">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Beta Updates Track" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nSh-L8-ca0" userLabel="Beta Track Label">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="159.66666666666666" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" ambiguous="YES" showsMenuAsPrimaryAction="YES" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" changesSelectionAsPrimaryAction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kYQ-zz-vjQ" userLabel="Beta Track Drop Down Button">
|
||||
<rect key="frame" x="301.66666666666669" y="8.3333333333333321" width="70.333333333333314" height="34.333333333333343"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="50" id="n1r-LA-2uh"/>
|
||||
</constraints>
|
||||
<buttonConfiguration key="configuration" style="plain" title="stable" titleAlignment="trailing"/>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="nSh-L8-ca0" firstAttribute="centerY" secondItem="V9s-7b-vkR" secondAttribute="centerY" id="5nr-a3-IIG"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="kYQ-zz-vjQ" secondAttribute="trailing" id="9U0-qE-bRy"/>
|
||||
<constraint firstItem="nSh-L8-ca0" firstAttribute="leading" secondItem="V9s-7b-vkR" secondAttribute="leadingMargin" id="kvL-1W-OhL"/>
|
||||
<constraint firstItem="kYQ-zz-vjQ" firstAttribute="centerY" secondItem="V9s-7b-vkR" secondAttribute="centerY" id="lQH-0l-98C"/>
|
||||
<constraint firstItem="kYQ-zz-vjQ" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="nSh-L8-ca0" secondAttribute="trailing" constant="16" id="uHX-7V-d7I"/>
|
||||
</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="3"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
||||
<cells>
|
||||
<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="1163.0000038146973" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1308.6666622161865" width="402" height="51"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Send Feedback" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pMI-Aj-nQF">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Send Feedback" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pMI-Aj-nQF">
|
||||
<rect key="frame" x="29.999999999999993" y="15.333333333333334" width="125.33333333333331" height="20.333333333333329"/>
|
||||
<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" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -773,19 +849,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<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="1214.0000038146973" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1359.6666622161865" width="402" height="51"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="View Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sni-07-q0M">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="View Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sni-07-q0M">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="187.66666666666666" height="20.333333333333329"/>
|
||||
<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" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -810,19 +886,19 @@
|
||||
</connections>
|
||||
</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="1265.0000038146973" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1410.6666622161865" width="402" 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="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideJITServer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="46q-DB-5nc">
|
||||
<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="29.999999999999993" y="15.333333333333334" width="115.33333333333331" height="20.333333333333329"/>
|
||||
<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" translatesAutoresizingMaskIntoConstraints="NO" id="wvD-eZ-nQI">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wvD-eZ-nQI">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -844,19 +920,19 @@
|
||||
</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">
|
||||
<rect key="frame" x="0.0" y="1316.0000038146973" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1461.6666622161865" width="402" height="51"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="140" height="20.333333333333329"/>
|
||||
<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" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -878,19 +954,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<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="1367.0000038146973" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1512.6666622161865" width="402" height="51"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Anisette Servers" 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.333333333333334" width="135.66666666666666" height="20.333333333333329"/>
|
||||
<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" translatesAutoresizingMaskIntoConstraints="NO" id="0dh-yd-7i9">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0dh-yd-7i9">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -911,31 +987,31 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="XW5-Zc-nXH" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1418.0000038146973" width="402" height="51"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="7rt-MT-kFH" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1563.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="XW5-Zc-nXH" id="AtM-bL-8pS">
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="7rt-MT-kFH" id="mZL-UA-6V0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable Beta Updates" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2px-HD-0UT">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="169" height="20.333333333333329"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Enable EMP for wireguard" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZH7-ZA-Epf" userLabel="Enable EMP for wireguard">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="209" height="20.333333333333329"/>
|
||||
<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="e32-w4-5fk">
|
||||
<rect key="frame" x="323" y="10" width="51" height="31"/>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8qE-hE-Ujn">
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleEnableBetaUpdates:" destination="aMk-Xp-UL8" eventType="valueChanged" id="uxG-df-7GK"/>
|
||||
<action selector="toggleEnableEMPforWireguard:" destination="aMk-Xp-UL8" eventType="valueChanged" id="B0Q-Jb-fox"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="2px-HD-0UT" firstAttribute="centerY" secondItem="AtM-bL-8pS" secondAttribute="centerY" id="07r-jt-3rz"/>
|
||||
<constraint firstItem="2px-HD-0UT" firstAttribute="leading" secondItem="AtM-bL-8pS" secondAttribute="leadingMargin" id="K2i-9G-bG8"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="e32-w4-5fk" secondAttribute="trailing" id="Wa7-m6-lcl"/>
|
||||
<constraint firstItem="e32-w4-5fk" firstAttribute="centerY" secondItem="AtM-bL-8pS" secondAttribute="centerY" id="n7R-av-FBX"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="8qE-hE-Ujn" secondAttribute="trailing" id="KgO-N1-7Bw"/>
|
||||
<constraint firstItem="ZH7-ZA-Epf" firstAttribute="leading" secondItem="mZL-UA-6V0" secondAttribute="leadingMargin" id="MBP-lj-8f5"/>
|
||||
<constraint firstItem="ZH7-ZA-Epf" firstAttribute="centerY" secondItem="mZL-UA-6V0" secondAttribute="centerY" id="Pht-1f-5K3"/>
|
||||
<constraint firstItem="8qE-hE-Ujn" firstAttribute="centerY" secondItem="mZL-UA-6V0" secondAttribute="centerY" id="vhM-Am-Jpo"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -946,33 +1022,31 @@
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="qbY-8c-LYT" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1469.0000038146973" width="402" height="51"/>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="q6e-PG-mTq" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1614.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="qbY-8c-LYT" id="NxK-qB-w7Q">
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="q6e-PG-mTq" id="PRJ-Ed-P86">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Beta Updates Track" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5J9-vR-vhX" userLabel="Beta Track Label">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="159.66666666666666" height="20.333333333333329"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Enable AppId Customization" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Fq4-2u-Lgd" userLabel="Enable AppId Customization">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="230" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" showsMenuAsPrimaryAction="YES" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" changesSelectionAsPrimaryAction="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Oct-iT-NwP" userLabel="Beta Track Drop Down Button">
|
||||
<rect key="frame" x="301.66666666666669" y="8.3333333333333321" width="70.333333333333314" height="34.333333333333343"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="50" id="new-width-constraint"/>
|
||||
</constraints>
|
||||
<buttonConfiguration key="configuration" style="plain" title="stable" titleAlignment="trailing"/>
|
||||
</button>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="fXx-wl-F5H">
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleEnableAppIdCustomization:" destination="aMk-Xp-UL8" eventType="valueChanged" id="gtP-5M-9Ms"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="5J9-vR-vhX" firstAttribute="centerY" secondItem="NxK-qB-w7Q" secondAttribute="centerY" id="5gk-uR-bun"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="Oct-iT-NwP" secondAttribute="trailing" id="Wa8-m6-lcl"/>
|
||||
<constraint firstItem="5J9-vR-vhX" firstAttribute="leading" secondItem="NxK-qB-w7Q" secondAttribute="leadingMargin" id="af8-XD-RSn"/>
|
||||
<constraint firstItem="Oct-iT-NwP" firstAttribute="centerY" secondItem="NxK-qB-w7Q" secondAttribute="centerY" id="n7t-av-FBX"/>
|
||||
<constraint firstItem="Oct-iT-NwP" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="5J9-vR-vhX" secondAttribute="trailing" constant="16" id="new-spacing-constraint"/>
|
||||
<constraint firstItem="Fq4-2u-Lgd" firstAttribute="centerY" secondItem="PRJ-Ed-P86" secondAttribute="centerY" id="4dM-eV-6Bh"/>
|
||||
<constraint firstItem="Fq4-2u-Lgd" firstAttribute="leading" secondItem="PRJ-Ed-P86" secondAttribute="leadingMargin" id="Pd8-US-Y4U"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="fXx-wl-F5H" secondAttribute="trailing" id="ZU9-iK-2ox"/>
|
||||
<constraint firstItem="fXx-wl-F5H" firstAttribute="centerY" secondItem="PRJ-Ed-P86" secondAttribute="centerY" id="fhs-rv-x3J"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -985,23 +1059,139 @@
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection id="ZhW-yK-wdJ">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="qjD-UK-myl" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1701.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="qjD-UK-myl" id="bcu-KT-Xee">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Import Account..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jts-UA-M8d">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="143" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="jts-UA-M8d" firstAttribute="centerY" secondItem="bcu-KT-Xee" secondAttribute="centerY" id="9Wq-8a-v4s"/>
|
||||
<constraint firstItem="jts-UA-M8d" firstAttribute="leading" secondItem="bcu-KT-Xee" secondAttribute="leadingMargin" id="rTG-U2-MOH"/>
|
||||
</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="1"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="dNh-fp-vBs" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1752.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="dNh-fp-vBs" id="Meb-tV-6br">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Export Account..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uHg-iq-d36">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="142.33333333333334" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="uHg-iq-d36" firstAttribute="leading" secondItem="Meb-tV-6br" secondAttribute="leadingMargin" id="P8L-Yt-APv"/>
|
||||
<constraint firstItem="uHg-iq-d36" firstAttribute="centerY" secondItem="Meb-tV-6br" secondAttribute="centerY" id="bkY-rp-t3t"/>
|
||||
</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="Y6h-Bo-yec" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1803.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Y6h-Bo-yec" id="4Jf-I6-v7z">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Import Signing Certificate..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rv6-S1-2gw">
|
||||
<rect key="frame" x="30.000000000000014" y="15.333333333333334" width="227.33333333333337" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="rv6-S1-2gw" firstAttribute="leading" secondItem="4Jf-I6-v7z" secondAttribute="leadingMargin" id="7zH-bg-kLS"/>
|
||||
<constraint firstItem="rv6-S1-2gw" firstAttribute="centerY" secondItem="4Jf-I6-v7z" secondAttribute="centerY" id="Yls-DF-HHr"/>
|
||||
</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="dLk-d6-X4T" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1854.6666622161865" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="dLk-d6-X4T" id="Okl-3m-rde">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Export Signing Certificate..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WPe-mj-W7t">
|
||||
<rect key="frame" x="29.999999999999986" y="15.333333333333334" width="226.66666666666663" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="WPe-mj-W7t" firstAttribute="leading" secondItem="Okl-3m-rde" secondAttribute="leadingMargin" id="9g7-9Z-ZZQ"/>
|
||||
<constraint firstItem="WPe-mj-W7t" firstAttribute="centerY" secondItem="Okl-3m-rde" secondAttribute="centerY" id="RiS-WT-srl"/>
|
||||
</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="3"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="" id="lLQ-K0-XSb">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="daQ-mk-yqC" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1560.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1941.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="daQ-mk-yqC" id="ZkW-ZR-twy">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Disable Response Caching" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jFh-36-AP2">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Disable Response Caching" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jFh-36-AP2">
|
||||
<rect key="frame" x="30.000000000000014" y="15.333333333333334" width="215.33333333333337" height="20.333333333333329"/>
|
||||
<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="AAh-cu-qw8">
|
||||
<rect key="frame" x="323" y="10" width="51" height="31"/>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AAh-cu-qw8">
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleDisableResponseCaching:" destination="aMk-Xp-UL8" eventType="valueChanged" id="lCm-qi-piH"/>
|
||||
</connections>
|
||||
@@ -1023,20 +1213,20 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="hRP-jU-2hd" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1611.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1992.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="hRP-jU-2hd" id="JhE-O4-pRg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Export Resigned Apps" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="d5F-bf-6kB">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Export Resigned Apps" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="d5F-bf-6kB">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="180" height="20.333333333333329"/>
|
||||
<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="GYP-qn-wzh">
|
||||
<rect key="frame" x="323" y="10" width="51" height="31"/>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="GYP-qn-wzh">
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleResignedAppExport:" destination="aMk-Xp-UL8" eventType="valueChanged" id="Z1k-xh-sjD"/>
|
||||
</connections>
|
||||
@@ -1058,20 +1248,20 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="JoN-Aj-XtZ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1662.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2043.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="JoN-Aj-XtZ" id="v8Q-VQ-Q1h">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable Verbose Ops Logging" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7bz-tI-tLY">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Enable Verbose Ops Logging" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7bz-tI-tLY">
|
||||
<rect key="frame" x="29.999999999999986" y="15.333333333333334" width="232.66666666666663" height="20.333333333333329"/>
|
||||
<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="Q5X-Mo-KpE">
|
||||
<rect key="frame" x="323" y="10" width="51" height="31"/>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Q5X-Mo-KpE">
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleVerboseOperationsLogging:" destination="aMk-Xp-UL8" eventType="valueChanged" id="n9N-Gt-OY2"/>
|
||||
</connections>
|
||||
@@ -1093,13 +1283,13 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="QOO-bO-4M5" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1713.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2094.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="QOO-bO-4M5" id="VTT-z5-C89">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Export Database..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ho1-To-wve" userLabel="Export Database">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Export Database..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ho1-To-wve" userLabel="Export Database">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="151.66666666666666" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -1121,13 +1311,13 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="ToB-H7-2lR" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1764.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2145.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ToB-H7-2lR" id="Acf-xV-Isn">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Delete Database..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CcF-9x-Eu8" userLabel="Delete Database Label">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Delete Database..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CcF-9x-Eu8" userLabel="Delete Database Label">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="150.33333333333334" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -1149,19 +1339,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="xtI-eU-LFb" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1815.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2196.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="xtI-eU-LFb" id="bc9-41-6mE">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Operations Logging Control" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="LW3-gm-lj5">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Operations Logging Control" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="LW3-gm-lj5">
|
||||
<rect key="frame" x="30.000000000000014" y="15.333333333333334" width="224.33333333333337" height="20.333333333333329"/>
|
||||
<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" translatesAutoresizingMaskIntoConstraints="NO" id="zl4-ti-HTW">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zl4-ti-HTW">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -1183,20 +1373,20 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="pvu-IV-Poa" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1866.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2247.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="pvu-IV-Poa" id="zck-an-8cK">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Recreate Database on Next Start" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZRk-8S-kBQ" userLabel="Recreate Database Label">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Recreate Database on Next Start" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZRk-8S-kBQ" userLabel="Recreate Database Label">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="265.33333333333331" height="20.333333333333329"/>
|
||||
<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="uGv-Lb-Ita" userLabel="Recreate DB switch">
|
||||
<rect key="frame" x="323" y="10" width="51" height="31"/>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uGv-Lb-Ita" userLabel="Recreate DB switch">
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleRecreateDatabaseSwitch:" destination="aMk-Xp-UL8" eventType="valueChanged" id="vlf-Iz-kWr"/>
|
||||
</connections>
|
||||
@@ -1218,20 +1408,20 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="9By-QW-Jw9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1917.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2298.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="9By-QW-Jw9" id="Dzq-gE-zyT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Minimuxer Console Logging" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jW6-pb-xdP">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Minimuxer Console Logging" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jW6-pb-xdP">
|
||||
<rect key="frame" x="30.000000000000014" y="15.333333333333334" width="225.33333333333337" height="20.333333333333329"/>
|
||||
<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="os8-7F-rSm" userLabel="Minimuxer logging Switch">
|
||||
<rect key="frame" x="323" y="10" width="51" height="31"/>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="os8-7F-rSm" userLabel="Minimuxer logging Switch">
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleMinimuxerConsoleLogging:" destination="aMk-Xp-UL8" eventType="valueChanged" id="d0C-kx-aFV"/>
|
||||
</connections>
|
||||
@@ -1246,6 +1436,41 @@
|
||||
</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>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="LzP-Qb-bmC" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="2349.3333282470703" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="LzP-Qb-bmC" id="3rE-h0-8kb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Minimuxer Status Check" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3BY-c1-QEV" userLabel="Minimuxer Status Check">
|
||||
<rect key="frame" x="30.000000000000014" y="15.333333333333334" width="198.33333333333337" height="20.333333333333329"/>
|
||||
<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" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="AB6-Ok-Faf" userLabel="Minimuxer Check Switch">
|
||||
<rect key="frame" x="311" y="11.666666666666664" width="63" height="28"/>
|
||||
<connections>
|
||||
<action selector="toggleMinimuxerStatusCheck:" destination="aMk-Xp-UL8" eventType="valueChanged" id="Kwh-Km-aLj"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="AB6-Ok-Faf" secondAttribute="trailing" id="EtH-X0-plP"/>
|
||||
<constraint firstItem="AB6-Ok-Faf" firstAttribute="centerY" secondItem="3rE-h0-8kb" secondAttribute="centerY" id="Tc3-56-q7s"/>
|
||||
<constraint firstItem="3BY-c1-QEV" firstAttribute="centerY" secondItem="3rE-h0-8kb" secondAttribute="centerY" id="kIV-xS-Ava"/>
|
||||
<constraint firstItem="3BY-c1-QEV" firstAttribute="leading" secondItem="3rE-h0-8kb" secondAttribute="leadingMargin" id="pJS-6V-6vh"/>
|
||||
</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="3"/>
|
||||
@@ -1266,11 +1491,13 @@
|
||||
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
|
||||
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
|
||||
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
|
||||
<outlet property="betaTrackLabel" destination="5J9-vR-vhX" id="x2k-X7-pUr"/>
|
||||
<outlet property="betaTrackPopupButton" destination="Oct-iT-NwP" id="sOR-cc-IWC"/>
|
||||
<outlet property="betaUpdatesSwitch" destination="e32-w4-5fk" id="kdn-ZR-cNU"/>
|
||||
<outlet property="betaTrackLabel" destination="1B5-BJ-Rkb" id="wcZ-in-EOa"/>
|
||||
<outlet property="betaTrackPopupButton" destination="kYQ-zz-vjQ" id="QXm-ud-70m"/>
|
||||
<outlet property="betaUpdatesSwitch" destination="Blb-Dp-9QF" id="C4e-rj-ocb"/>
|
||||
<outlet property="customizeAppIdSwitch" destination="fXx-wl-F5H" id="Yc0-ZL-aDs"/>
|
||||
<outlet property="disableAppLimitSwitch" destination="1aa-og-ZXD" id="oVL-Md-yZ8"/>
|
||||
<outlet property="disableResponseCachingSwitch" destination="AAh-cu-qw8" id="aVT-Md-yZ8"/>
|
||||
<outlet property="enableEMPforWireguard" destination="8qE-hE-Ujn" id="VC2-PV-cea"/>
|
||||
<outlet property="exportResignedAppsSwitch" destination="GYP-qn-wzh" id="aVL-Md-yZ8"/>
|
||||
<outlet property="githubButton" destination="oqj-4S-I9l" id="sxB-LE-gA2"/>
|
||||
<outlet property="mastodonButton" destination="B8Q-e7-beR" id="Kbe-Og-rsg"/>
|
||||
@@ -1295,8 +1522,9 @@
|
||||
<toolbarItems/>
|
||||
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
|
||||
<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="62" width="402" height="96"/>
|
||||
<rect key="frame" x="0.0" y="124" width="402" height="106"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="barTintColor" name="SettingsBackground"/>
|
||||
<textAttributes key="titleTextAttributes">
|
||||
@@ -1328,14 +1556,14 @@
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="8Xf-RE-QJx" customClass="RefreshAttemptTableViewCell">
|
||||
<rect key="frame" x="0.0" y="50" width="402" height="64.666664123535156"/>
|
||||
<rect key="frame" x="0.0" y="50" width="402" height="73.333335876464844"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="8Xf-RE-QJx" id="r3G-oh-AyQ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="64.666664123535156"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="73.333335876464844"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="SN9-pA-GDU">
|
||||
<rect key="frame" x="20" y="10.999999999999996" width="362" height="42.666666666666657"/>
|
||||
<rect key="frame" x="20" y="15.000000000000004" width="362" height="43.333333333333343"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="SqJ-wP-gO1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="362" height="20.333333333333332"/>
|
||||
@@ -1355,7 +1583,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Could not connect to SideStore." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7L1-AA-2yo">
|
||||
<rect key="frame" x="0.0" y="24.333333333333336" width="362" height="18.333333333333336"/>
|
||||
<rect key="frame" x="0.0" y="24.333333333333336" width="362" height="19"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
@@ -1396,7 +1624,7 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<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="106" width="402" height="685"/>
|
||||
<rect key="frame" x="0.0" y="178" width="402" height="579"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<string key="text">Jay Freeman (ldid)
|
||||
Copyright (C) 2007-2012 Jay Freeman (saurik)
|
||||
@@ -1540,33 +1768,7 @@ Settings by i cons from the Noun Project</string>
|
||||
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
||||
<inset key="sectionInset" minX="20" minY="8" maxX="20" maxY="0.0"/>
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="T6v-Rq-ntX" customClass="PatronCollectionViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="8" width="157" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="157" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Caroline Moore" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.25" translatesAutoresizingMaskIntoConstraints="NO" id="ahr-fF-k3e">
|
||||
<rect key="frame" x="0.0" y="0.0" width="157" height="20"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" white="1" alpha="0.75" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</view>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="ahr-fF-k3e" secondAttribute="trailing" id="9aF-2y-sZf"/>
|
||||
<constraint firstItem="ahr-fF-k3e" firstAttribute="top" secondItem="T6v-Rq-ntX" secondAttribute="top" id="M89-x2-VnS"/>
|
||||
<constraint firstItem="ahr-fF-k3e" firstAttribute="leading" secondItem="T6v-Rq-ntX" secondAttribute="leading" id="THC-sX-gVq"/>
|
||||
<constraint firstAttribute="bottom" secondItem="ahr-fF-k3e" secondAttribute="bottom" id="loA-GD-3td"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="textLabel" destination="ahr-fF-k3e" id="xql-Ch-bfh"/>
|
||||
</connections>
|
||||
</collectionViewCell>
|
||||
</cells>
|
||||
<cells/>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="dp8-8j-vt9" id="ONG-kb-M7N"/>
|
||||
<outlet property="delegate" destination="dp8-8j-vt9" id="790-Kr-6l7"/>
|
||||
@@ -1595,17 +1797,17 @@ Settings by i cons from the Noun Project</string>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="HAm-mA-O78" customClass="ErrorLogTableViewCell">
|
||||
<rect key="frame" x="20" y="55.333332061767578" width="362" height="107.33333587646484"/>
|
||||
<rect key="frame" x="20" y="55.333332061767578" width="362" height="116"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="HAm-mA-O78" id="swa-et-rfA">
|
||||
<rect key="frame" x="0.0" y="0.0" width="362" height="107.33333587646484"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="362" height="116"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="mtw-JM-T70">
|
||||
<rect key="frame" x="20" y="11" width="322" height="85.333333333333329"/>
|
||||
<rect key="frame" x="16" y="15" width="330" height="86"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="top" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="bjU-TX-4lm" userLabel="Compact">
|
||||
<rect key="frame" x="0.0" y="0.0" width="322" height="43.333333333333336"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="330" height="44"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="sDZ-ZN-NT1" customClass="AppIconImageView" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="43" height="43"/>
|
||||
@@ -1615,10 +1817,10 @@ Settings by i cons from the Noun Project</string>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="82d-v0-RCp">
|
||||
<rect key="frame" x="51" y="0.0" width="271" height="39"/>
|
||||
<rect key="frame" x="51" y="0.0" width="279" height="39"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="Q2j-Tc-bp2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="271" height="18"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="279" height="18"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Success" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="Na7-uj-XYZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="60" height="18"/>
|
||||
@@ -1627,7 +1829,7 @@ Settings by i cons from the Noun Project</string>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="Date" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SGf-pP-RL0">
|
||||
<rect key="frame" x="240.66666666666669" y="0.0" width="30.333333333333314" height="18"/>
|
||||
<rect key="frame" x="248.66666666666671" y="0.0" width="30.333333333333343" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -1635,7 +1837,7 @@ Settings by i cons from the Noun Project</string>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Error Code" textAlignment="natural" lineBreakMode="headTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="R5a-wv-xHd">
|
||||
<rect key="frame" x="0.0" y="22" width="271" height="17"/>
|
||||
<rect key="frame" x="0.0" y="22" width="279" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -1645,7 +1847,7 @@ Settings by i cons from the Noun Project</string>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Error Description" textAlignment="natural" selectable="NO" layoutManager="textKit1" translatesAutoresizingMaskIntoConstraints="NO" id="1df-ri-hKN" customClass="CollapsingTextView" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="51.333333333333343" width="322" height="34"/>
|
||||
<rect key="frame" x="0.0" y="52" width="330" height="34"/>
|
||||
<accessibility key="accessibilityConfiguration">
|
||||
<accessibilityTraits key="traits" staticText="YES"/>
|
||||
<bool key="isElement" value="NO"/>
|
||||
@@ -1657,7 +1859,7 @@ Settings by i cons from the Noun Project</string>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<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="362" height="107.33333333333333"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="362" height="116"/>
|
||||
<accessibility key="accessibilityConfiguration">
|
||||
<bool key="isElement" value="NO"/>
|
||||
</accessibility>
|
||||
@@ -1728,11 +1930,11 @@ Settings by i cons from the Noun Project</string>
|
||||
<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="402" height="864"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="ctd-NB-4ov">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="864"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="812"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<color key="textColor" systemColor="labelColor"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
@@ -1770,8 +1972,9 @@ Settings by i cons from the Noun Project</string>
|
||||
<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="402" height="56"/>
|
||||
<rect key="frame" x="0.0" y="16" width="402" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
|
||||
@@ -16,10 +16,12 @@ import IntentsUI
|
||||
import SemanticVersion
|
||||
|
||||
import AltStoreCore
|
||||
import CAltSign
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
extension SettingsViewController
|
||||
{
|
||||
fileprivate enum Section: Int, CaseIterable
|
||||
private enum Section: Int, CaseIterable
|
||||
{
|
||||
case signIn
|
||||
case account
|
||||
@@ -29,13 +31,14 @@ extension SettingsViewController
|
||||
case instructions
|
||||
case techyThings
|
||||
case credits
|
||||
case betaTesting
|
||||
case advancedSettings
|
||||
// diagnostics section, will be enabled on release builds only on swipe down with 3 fingers 3 times
|
||||
case diagnostics
|
||||
case signing
|
||||
case diagnostics // diagnostics section, will be enabled on release builds only on swipe down with 3 fingers 3 times
|
||||
// case macDirtyCow
|
||||
}
|
||||
|
||||
fileprivate enum AppRefreshRow: Int, CaseIterable
|
||||
private enum AppRefreshRow: Int, CaseIterable
|
||||
{
|
||||
case backgroundRefresh
|
||||
case noIdleTimeout
|
||||
@@ -54,7 +57,7 @@ extension SettingsViewController
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate enum CreditsRow: Int, CaseIterable
|
||||
private enum CreditsRow: Int, CaseIterable
|
||||
{
|
||||
case developer
|
||||
case operations
|
||||
@@ -62,25 +65,36 @@ extension SettingsViewController
|
||||
case softwareLicenses
|
||||
}
|
||||
|
||||
fileprivate enum TechyThingsRow: Int, CaseIterable
|
||||
private enum TechyThingsRow: Int, CaseIterable
|
||||
{
|
||||
case errorLog
|
||||
case clearCache
|
||||
}
|
||||
|
||||
fileprivate enum AdvancedSettingsRow: Int, CaseIterable
|
||||
private enum AdvancedSettingsRow: Int, CaseIterable
|
||||
{
|
||||
case sendFeedback
|
||||
case refreshAttempts
|
||||
case refreshSideJITServer
|
||||
case resetPairingFile
|
||||
case anisetteServers
|
||||
case betaUpdates
|
||||
case betaTrack
|
||||
// case hiddenSettings
|
||||
case enableEMPForWiregaurd
|
||||
case customizeAppId
|
||||
}
|
||||
|
||||
private enum SigningSettingsRow: Int, CaseIterable {
|
||||
case importAccount
|
||||
case exportAccount
|
||||
case importCert
|
||||
case exportCert
|
||||
}
|
||||
|
||||
fileprivate enum DiagnosticsRow: Int, CaseIterable
|
||||
private enum BetaTestingRow: Int, CaseIterable {
|
||||
case betaUpdates
|
||||
case betaTrack
|
||||
}
|
||||
|
||||
private enum DiagnosticsRow: Int, CaseIterable
|
||||
{
|
||||
case responseCaching
|
||||
case exportResignedApp
|
||||
@@ -111,9 +125,11 @@ final class SettingsViewController: UITableViewController
|
||||
@IBOutlet private var accountTypeLabel: UILabel!
|
||||
|
||||
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
|
||||
@IBOutlet private var enableEMPforWireguard: UISwitch!
|
||||
@IBOutlet private var noIdleTimeoutSwitch: UISwitch!
|
||||
@IBOutlet private var disableAppLimitSwitch: UISwitch!
|
||||
@IBOutlet private var betaUpdatesSwitch: UISwitch!
|
||||
@IBOutlet private var customizeAppIdSwitch: UISwitch!
|
||||
@IBOutlet private var exportResignedAppsSwitch: UISwitch!
|
||||
@IBOutlet private var verboseOperationsLoggingSwitch: UISwitch!
|
||||
@IBOutlet private var minimuxerConsoleLoggingSwitch: UISwitch!
|
||||
@@ -143,6 +159,7 @@ final class SettingsViewController: UITableViewController
|
||||
|
||||
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)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openExportCertificateConfirm(_:)), name: AppDelegate.exportCertificateNotification, object: nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -191,6 +208,16 @@ final class SettingsViewController: UITableViewController
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
// --- iOS 26 fix ---
|
||||
if #available(iOS 26.0, *) {
|
||||
let appearance = UINavigationBarAppearance()
|
||||
// appearance.configureWithOpaqueBackground() // or .defaultBackground if you want blur
|
||||
// appearance.backgroundColor = UIColor(named: "SettingsBackground")
|
||||
appearance.titleTextAttributes = [.foregroundColor: UIColor.white]
|
||||
appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
|
||||
navigationController?.navigationBar.standardAppearance = appearance
|
||||
navigationController?.navigationBar.scrollEdgeAppearance = appearance // required for iOS 26, maybe enforce it in storyboard?
|
||||
}
|
||||
let nib = UINib(nibName: "SettingsHeaderFooterView", bundle: nil)
|
||||
self.prototypeHeaderFooterView = nib.instantiate(withOwner: nil, options: nil)[0] as? SettingsHeaderFooterView
|
||||
|
||||
@@ -236,6 +263,118 @@ final class SettingsViewController: UITableViewController
|
||||
}
|
||||
|
||||
configureReleaseChannelButton()
|
||||
#if !targetEnvironment(simulator)
|
||||
detectAndImportAccountFile()
|
||||
#endif
|
||||
}
|
||||
|
||||
func importAccountAtFile(_ file: URL, remove: Bool = false) {
|
||||
_ = file.startAccessingSecurityScopedResource()
|
||||
defer { file.stopAccessingSecurityScopedResource() }
|
||||
guard let accountD = try? Data(contentsOf: file) else {
|
||||
return Logger.main.notice("Could not parse data from file \(file)")
|
||||
}
|
||||
guard let account = try? Foundation.JSONDecoder().decode(ImportedAccount.self, from: accountD) else {
|
||||
return Logger.main.notice("Could not parse data from file \(file)")
|
||||
}
|
||||
print("We want to import this account probably: \(account)")
|
||||
if remove {
|
||||
try? FileManager.default.removeItem(at: file)
|
||||
}
|
||||
Keychain.shared.appleIDEmailAddress = account.email
|
||||
Keychain.shared.appleIDPassword = account.password
|
||||
Keychain.shared.adiPb = account.adiPB
|
||||
Keychain.shared.identifier = account.local_user
|
||||
signIn()
|
||||
update()
|
||||
if let altCert = ALTCertificate(p12Data: account.cert, password: account.certpass) {
|
||||
Keychain.shared.signingCertificate = altCert.encryptedP12Data(withPassword: "")!
|
||||
Keychain.shared.signingCertificatePassword = account.certpass
|
||||
let toastView = ToastView(text: NSLocalizedString("Successfully imported '\(account.email)'!", comment: ""), detailText: "SideStore should be fully operational!")
|
||||
return toastView.show(in: self)
|
||||
} else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to import account certificate!", comment: ""), detailText: "Failed to create ALTCertificate. Check if the password is correct. Still imported account/adi.pb details!")
|
||||
return toastView.show(in: self)
|
||||
}
|
||||
}
|
||||
|
||||
func detectAndImportAccountFile() {
|
||||
let accountFileURL = FileManager.default.documentsDirectory.appendingPathComponent("Account.sideconf")
|
||||
#if !DEBUG
|
||||
importAccountAtFile(accountFileURL, remove: true)
|
||||
#else
|
||||
importAccountAtFile(accountFileURL)
|
||||
#endif
|
||||
}
|
||||
|
||||
func exportAccount(_ certpass: String) -> ImportedAccount? {
|
||||
guard let email = Keychain.shared.appleIDEmailAddress,
|
||||
let password = Keychain.shared.appleIDPassword,
|
||||
let cert = Keychain.shared.signingCertificate,
|
||||
let identifier = Keychain.shared.identifier,
|
||||
let adiPB = Keychain.shared.adiPb else {
|
||||
#if DEBUG
|
||||
print(Keychain.shared.appleIDEmailAddress ?? "Empty email")
|
||||
print(Keychain.shared.appleIDPassword ?? "Empty password")
|
||||
print(Keychain.shared.signingCertificate ?? "Empty cert")
|
||||
print(Keychain.shared.identifier ?? "Empty identifier")
|
||||
print(Keychain.shared.adiPb ?? "Empty adiPb")
|
||||
#endif
|
||||
return nil
|
||||
}
|
||||
return ImportedAccount(email: email, password: password, cert: cert, certpass: certpass, local_user: identifier, adiPB: adiPB)
|
||||
}
|
||||
|
||||
func showExportAccount() {
|
||||
|
||||
Task {
|
||||
guard let password = await withUnsafeContinuation({ (c: UnsafeContinuation<String?,Never>) in
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Please enter the password for the certificate.", comment: ""), message: nil, preferredStyle: .alert)
|
||||
|
||||
alertController.addTextField { (textField) in
|
||||
textField.autocorrectionType = .no
|
||||
textField.autocapitalizationType = .none
|
||||
}
|
||||
|
||||
let submitAction = UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default) { (action) in
|
||||
let textField = alertController.textFields?.first
|
||||
|
||||
let code = textField?.text ?? ""
|
||||
c.resume(returning: code)
|
||||
}
|
||||
alertController.addAction(submitAction)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { (action) in
|
||||
c.resume(returning: nil)
|
||||
})
|
||||
|
||||
self.present(alertController, animated: true)
|
||||
}) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let account = exportAccount(password) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to export account!", comment: ""), detailText: "Account not found.")
|
||||
return toastView.show(in: self)
|
||||
}
|
||||
|
||||
guard let accountData = try? Foundation.JSONEncoder().encode(account) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to export account data!", comment: ""), detailText: "Account malformed.")
|
||||
toastView.show(in: self)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
let accountTmpPath = FileManager.default.temporaryDirectory.appendingPathComponent("\(account.email).sideconf")
|
||||
do {
|
||||
try accountData.write(to: accountTmpPath)
|
||||
} catch {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to export account!", comment: ""), detailText: error.localizedDescription)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
let exportVC = UIDocumentPickerViewController(forExporting: [accountTmpPath], asCopy: false)
|
||||
self.present(exportVC, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool)
|
||||
@@ -335,12 +474,15 @@ private extension SettingsViewController
|
||||
|
||||
// AppRefreshRow
|
||||
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
|
||||
self.enableEMPforWireguard.isOn = UserDefaults.standard.enableEMPforWireguard
|
||||
self.noIdleTimeoutSwitch.isOn = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||
self.disableAppLimitSwitch.isOn = UserDefaults.standard.isAppLimitDisabled
|
||||
|
||||
// AdvancedSettingsRow
|
||||
self.customizeAppIdSwitch.isOn = UserDefaults.standard.customizeAppId
|
||||
|
||||
// BetaTestingRow
|
||||
self.betaUpdatesSwitch.isOn = UserDefaults.standard.isBetaUpdatesEnabled
|
||||
self.betaTrackLabel.isEnabled = UserDefaults.standard.isBetaUpdatesEnabled
|
||||
self.betaTrackPopupButton.isEnabled = UserDefaults.standard.isBetaUpdatesEnabled
|
||||
|
||||
// DiagnosticsRow
|
||||
@@ -357,7 +499,7 @@ private extension SettingsViewController
|
||||
}
|
||||
}
|
||||
|
||||
func prepare(_ settingsHeaderFooterView: SettingsHeaderFooterView, for section: Section, isHeader: Bool)
|
||||
private func prepare(_ settingsHeaderFooterView: SettingsHeaderFooterView, for section: Section, isHeader: Bool)
|
||||
{
|
||||
settingsHeaderFooterView.primaryLabel.isHidden = !isHeader
|
||||
settingsHeaderFooterView.secondaryLabel.isHidden = isHeader
|
||||
@@ -433,7 +575,35 @@ private extension SettingsViewController
|
||||
|
||||
case .advancedSettings:
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("ADVANCED SETTINGS", comment: "")
|
||||
|
||||
case .signing:
|
||||
if isHeader
|
||||
{
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("SIGNING", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("", comment: "")
|
||||
}
|
||||
|
||||
case .betaTesting:
|
||||
if isHeader
|
||||
{
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("BETA TESTING", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString(
|
||||
"""
|
||||
Opt in for beta testing to receive regular updates and early previews of upcoming releases.\n
|
||||
Please note that these builds are experimental and may be unstable or break unexpectedly.
|
||||
""",
|
||||
comment: ""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
case .diagnostics:
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("DIAGNOSTICS", comment: "")
|
||||
|
||||
@@ -450,7 +620,7 @@ private extension SettingsViewController
|
||||
}
|
||||
}
|
||||
|
||||
func preferredHeight(for settingsHeaderFooterView: SettingsHeaderFooterView, in section: Section, isHeader: Bool) -> CGFloat
|
||||
private func preferredHeight(for settingsHeaderFooterView: SettingsHeaderFooterView, in section: Section, isHeader: Bool) -> CGFloat
|
||||
{
|
||||
let widthConstraint = settingsHeaderFooterView.contentView.widthAnchor.constraint(equalToConstant: tableView.bounds.width)
|
||||
NSLayoutConstraint.activate([widthConstraint])
|
||||
@@ -462,7 +632,7 @@ private extension SettingsViewController
|
||||
return size.height
|
||||
}
|
||||
|
||||
func isSectionHidden(_ section: Section) -> Bool
|
||||
private func isSectionHidden(_ section: Section) -> Bool
|
||||
{
|
||||
switch section
|
||||
{
|
||||
@@ -553,6 +723,11 @@ private extension SettingsViewController
|
||||
UserDefaults.standard.isMinimuxerConsoleLoggingEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleMinimuxerStatusCheck(_ sender: UISwitch) {
|
||||
// update it in database
|
||||
UserDefaults.standard.isMinimuxerStatusCheckEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleRecreateDatabaseSwitch(_ sender: UISwitch) {
|
||||
// Update the setting in UserDefaults
|
||||
UserDefaults.standard.recreateDatabaseOnNextStart = sender.isOn
|
||||
@@ -590,11 +765,21 @@ private extension SettingsViewController
|
||||
UserDefaults.standard.isBetaUpdatesEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleEnableAppIdCustomization(_ sender: UISwitch) {
|
||||
// update it in database
|
||||
UserDefaults.standard.customizeAppId = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleIsBackgroundRefreshEnabled(_ sender: UISwitch)
|
||||
{
|
||||
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleEnableEMPforWireguard(_ sender: UISwitch)
|
||||
{
|
||||
UserDefaults.standard.enableEMPforWireguard = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleNoIdleTimeoutEnabled(_ sender: UISwitch)
|
||||
{
|
||||
UserDefaults.standard.isIdleTimeoutDisableEnabled = sender.isOn
|
||||
@@ -756,6 +941,48 @@ private extension SettingsViewController
|
||||
self.performSegue(withIdentifier: "showErrorLog", sender: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func openExportCertificateConfirm(_ notification: Notification)
|
||||
{
|
||||
func export()
|
||||
{
|
||||
guard let template = notification.userInfo?[AppDelegate.exportCertificateCallbackTemplateKey] as? String,
|
||||
template.contains("$(BASE64_CERT)") else {
|
||||
let toastView = ToastView(text: NSLocalizedString("No $(BASE64_CERT) placeholder found", comment: ""), detailText: nil)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
guard let data = Keychain.shared.signingCertificate,
|
||||
let password = Keychain.shared.signingCertificatePassword else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to find certificate or password", comment: ""), detailText: nil)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
let base64encodedCert = data.base64EncodedString()
|
||||
var allowedQueryParamAndKey = NSCharacterSet.urlQueryAllowed
|
||||
allowedQueryParamAndKey.remove(charactersIn: ";/?:@&=+$, ")
|
||||
guard let encodedCert = base64encodedCert.addingPercentEncoding(withAllowedCharacters: allowedQueryParamAndKey) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to encode certificate!", comment: ""), detailText: nil)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
var urlStr = template.replacingOccurrences(of: "$(BASE64_CERT)", with: encodedCert, options: .literal, range: nil)
|
||||
urlStr = urlStr.replacingOccurrences(of: "$(PASSWORD)", with: password, options: .literal, range: nil)
|
||||
|
||||
print(urlStr)
|
||||
guard let callbackUrl = URL(string: urlStr) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to initialize callback URL!", comment: ""), detailText: nil)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
UIApplication.shared.open(callbackUrl)
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Export Certificate", comment: ""), message: NSLocalizedString("Do you want to export your certificate to an external app? That app will be able to sign apps using your certificate.", comment: ""), preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Export", comment: ""), style: .default) { _ in export() })
|
||||
alertController.addAction(.cancel)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension SettingsViewController
|
||||
@@ -827,7 +1054,7 @@ extension SettingsViewController
|
||||
case _ where isSectionHidden(section): return nil
|
||||
case .signIn where self.activeTeam != nil: return nil
|
||||
case .account where self.activeTeam == nil: return nil
|
||||
case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .diagnostics /* ,.macDirtyCow */:
|
||||
case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .signing, .betaTesting, .diagnostics /* ,.macDirtyCow */:
|
||||
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||
self.prepare(headerView, for: section, isHeader: true)
|
||||
return headerView
|
||||
@@ -844,7 +1071,7 @@ extension SettingsViewController
|
||||
case _ where isSectionHidden(section): return nil
|
||||
case .signIn where self.activeTeam != nil: return nil
|
||||
// case .signIn, .patreon, .display, .appRefresh, .techyThings, .macDirtyCow:
|
||||
case .signIn, .patreon, .display, .appRefresh, .techyThings:
|
||||
case .signIn, .patreon, .display, .appRefresh, .techyThings, .signing, .betaTesting:
|
||||
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||
self.prepare(footerView, for: section, isHeader: false)
|
||||
return footerView
|
||||
@@ -861,8 +1088,7 @@ extension SettingsViewController
|
||||
case _ where isSectionHidden(section): return 1.0
|
||||
case .signIn where self.activeTeam != nil: return 1.0
|
||||
case .account where self.activeTeam == nil: return 1.0
|
||||
// case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .macDirtyCow, .advanced:
|
||||
case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .diagnostics:
|
||||
case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .signing, .betaTesting, .diagnostics:
|
||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true)
|
||||
return height
|
||||
|
||||
@@ -879,7 +1105,7 @@ extension SettingsViewController
|
||||
case .signIn where self.activeTeam != nil: return 1.0
|
||||
case .account where self.activeTeam == nil: return 1.0
|
||||
// case .signIn, .patreon, .display, .appRefresh, .techyThings, .macDirtyCow:
|
||||
case .signIn, .patreon, .display, .appRefresh, .techyThings, .diagnostics:
|
||||
case .signIn, .patreon, .display, .appRefresh, .techyThings, .signing, .diagnostics, .betaTesting:
|
||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
|
||||
return height
|
||||
|
||||
@@ -1142,17 +1368,162 @@ extension SettingsViewController
|
||||
let anisetteServersController = UIHostingController(rootView: anisetteServersView)
|
||||
|
||||
self.prepare(for: UIStoryboardSegue(identifier: "anisetteServers", source: self, destination: anisetteServersController), sender: nil)
|
||||
|
||||
// case .hiddenSettings:
|
||||
// // Create the URL that deep links to your app's custom settings.
|
||||
// if let url = URL(string: UIApplication.openSettingsURLString) {
|
||||
// // Ask the system to open that URL.
|
||||
// UIApplication.shared.open(url)
|
||||
// } else {
|
||||
// ELOG("UIApplication.openSettingsURLString invalid")
|
||||
// }
|
||||
case .refreshAttempts, .betaUpdates, .betaTrack: break
|
||||
case .refreshAttempts, .enableEMPForWiregaurd, .customizeAppId: break
|
||||
}
|
||||
case .signing:
|
||||
let row = SigningSettingsRow.allCases[indexPath.row]
|
||||
switch row {
|
||||
case .exportAccount: showExportAccount()
|
||||
case .importAccount:
|
||||
Task {
|
||||
let confUrl = await withUnsafeContinuation { c in
|
||||
let importVc = UIDocumentPickerViewController(forOpeningContentTypes: [UTType(filenameExtension: "sideconf")!], asCopy: false)
|
||||
ImportExport.documentPickerHandler = DocumentPickerHandler { url in
|
||||
c.resume(returning: url)
|
||||
}
|
||||
importVc.delegate = ImportExport.documentPickerHandler
|
||||
|
||||
self.present(importVc, animated: true)
|
||||
|
||||
}
|
||||
guard let confUrl else {
|
||||
return
|
||||
}
|
||||
importAccountAtFile(confUrl)
|
||||
}
|
||||
case .importCert:
|
||||
let importVc = UIDocumentPickerViewController(forOpeningContentTypes: [UTType(filenameExtension: "p12")!], asCopy: false)
|
||||
ImportExport.documentPickerHandler = DocumentPickerHandler { url in
|
||||
guard let url else {
|
||||
return
|
||||
}
|
||||
importVc.delegate = ImportExport.documentPickerHandler
|
||||
self.present(importVc, animated: true)
|
||||
_ = url.startAccessingSecurityScopedResource()
|
||||
defer { url.stopAccessingSecurityScopedResource() }
|
||||
}
|
||||
Task {
|
||||
let certUrl = await withUnsafeContinuation { c in
|
||||
let importVc = UIDocumentPickerViewController(forOpeningContentTypes: [UTType(filenameExtension: "p12")!], asCopy: false)
|
||||
ImportExport.documentPickerHandler = DocumentPickerHandler { url in
|
||||
_ = url?.startAccessingSecurityScopedResource()
|
||||
defer { url?.stopAccessingSecurityScopedResource() }
|
||||
c.resume(returning: url)
|
||||
}
|
||||
importVc.delegate = ImportExport.documentPickerHandler
|
||||
|
||||
self.present(importVc, animated: true)
|
||||
|
||||
}
|
||||
guard let certUrl else {
|
||||
return
|
||||
}
|
||||
|
||||
let password = await withUnsafeContinuation { (c: UnsafeContinuation<String?,Never>) in
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Please enter the password for the certificate.", comment: ""), message: nil, preferredStyle: .alert)
|
||||
|
||||
alertController.addTextField { (textField) in
|
||||
textField.autocorrectionType = .no
|
||||
textField.autocapitalizationType = .none
|
||||
}
|
||||
|
||||
let submitAction = UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default) { (action) in
|
||||
let textField = alertController.textFields?.first
|
||||
|
||||
let code = textField?.text ?? ""
|
||||
c.resume(returning: code)
|
||||
}
|
||||
alertController.addAction(submitAction)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { (action) in
|
||||
c.resume(returning: nil)
|
||||
})
|
||||
|
||||
self.present(alertController, animated: true)
|
||||
}
|
||||
|
||||
guard let password else {
|
||||
return
|
||||
}
|
||||
_ = certUrl.startAccessingSecurityScopedResource()
|
||||
defer {
|
||||
certUrl.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
let certData : Data
|
||||
do {
|
||||
certData = try Data(contentsOf: certUrl)
|
||||
} catch {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to import certificate!", comment: ""), detailText: error.localizedDescription)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
guard let altCert = ALTCertificate(p12Data: certData, password: password) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to import certificate!", comment: ""), detailText: "Failed to create ALTCertificate. Check if the password is correct.")
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
Keychain.shared.signingCertificate = altCert.encryptedP12Data(withPassword: "")!
|
||||
let toastView = ToastView(text: NSLocalizedString("Certificate imported successfully!", comment: ""), detailText: nil)
|
||||
toastView.show(in: self)
|
||||
}
|
||||
case .exportCert:
|
||||
Task {
|
||||
guard let certData = Keychain.shared.signingCertificate else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to export certificate!", comment: ""), detailText: "Certificate not found.")
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
let password = await withUnsafeContinuation { (c: UnsafeContinuation<String?,Never>) in
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Please enter the password for the certificate.", comment: ""), message: nil, preferredStyle: .alert)
|
||||
|
||||
alertController.addTextField { (textField) in
|
||||
textField.autocorrectionType = .no
|
||||
textField.autocapitalizationType = .none
|
||||
}
|
||||
|
||||
let submitAction = UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default) { (action) in
|
||||
let textField = alertController.textFields?.first
|
||||
|
||||
let code = textField?.text ?? ""
|
||||
c.resume(returning: code)
|
||||
}
|
||||
alertController.addAction(submitAction)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { (action) in
|
||||
c.resume(returning: nil)
|
||||
})
|
||||
|
||||
self.present(alertController, animated: true)
|
||||
}
|
||||
|
||||
guard let password else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let altCert = ALTCertificate(p12Data: certData, password: nil) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to export certificate!", comment: ""), detailText: "Failed to create ALTCertificate. Check if the password is correct.")
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
guard let newCertData = altCert.encryptedP12Data(withPassword: password) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to export certificate!", comment: ""), detailText: "Failed to encrypt ALTCertificate.")
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
let newCertTmpPath = FileManager.default.temporaryDirectory.appendingPathComponent("SideStoreSigningCertificate.p12")
|
||||
do {
|
||||
try newCertData.write(to: newCertTmpPath)
|
||||
} catch {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to export certificate!", comment: ""), detailText: error.localizedDescription)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
let exportVC = UIDocumentPickerViewController(forExporting: [newCertTmpPath], asCopy: false)
|
||||
self.present(exportVC, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
case .diagnostics:
|
||||
@@ -1206,7 +1577,7 @@ extension SettingsViewController
|
||||
|
||||
|
||||
// case .account, .patreon, .display, .instructions, .macDirtyCow: break
|
||||
case .account, .patreon, .display, .instructions: break
|
||||
case .account, .patreon, .display, .instructions, .betaTesting: break
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="xWh-1U-u0q" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="59" width="393" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
@@ -248,6 +249,7 @@
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" largeTitles="YES" id="HLe-3g-P8I" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="108"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
|
||||
@@ -46,6 +46,14 @@ final class SourcesViewController: UICollectionViewController
|
||||
{
|
||||
super.viewDidLoad()
|
||||
|
||||
// Ensure large titles
|
||||
navigationController?.navigationBar.prefersLargeTitles = true
|
||||
navigationItem.largeTitleDisplayMode = .automatic
|
||||
|
||||
// Set title
|
||||
navigationItem.title = "Sources"
|
||||
navigationController?.navigationBar.layoutMargins.left = 20
|
||||
|
||||
let layout = self.makeLayout()
|
||||
self.collectionView.collectionViewLayout = layout
|
||||
|
||||
|
||||
@@ -36,6 +36,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.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.exportFiles(_:)), name: AppDelegate.exportCertificateNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openErrorLog(_:)), name: ToastView.openErrorLogNotification, object: nil)
|
||||
}
|
||||
|
||||
@@ -141,4 +142,9 @@ private extension TabBarController
|
||||
{
|
||||
self.selectedIndex = Tab.settings.rawValue
|
||||
}
|
||||
|
||||
@objc func exportFiles(_ notification: Notification)
|
||||
{
|
||||
self.selectedIndex = Tab.settings.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
18
AltStore/Types/ImportedAccount.swift
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// ImportedAccount.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by ny on 9/7/25.
|
||||
// Copyright © 2025 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct ImportedAccount: Codable {
|
||||
let email: String
|
||||
let password: String
|
||||
let cert: Data
|
||||
let certpass: String
|
||||
let local_user: String
|
||||
let adiPB: String
|
||||
}
|
||||
@@ -53,6 +53,12 @@ public class Keychain
|
||||
@KeychainItem(key: "appleIDPassword")
|
||||
public var appleIDPassword: String?
|
||||
|
||||
@KeychainItem(key: "appleIDAdsid")
|
||||
public var appleIDAdsid: String?
|
||||
|
||||
@KeychainItem(key: "appleIDXcodeToken")
|
||||
public var appleIDXcodeToken: String?
|
||||
|
||||
@KeychainItem(key: "signingCertificatePrivateKey")
|
||||
public var signingCertificatePrivateKey: Data?
|
||||
|
||||
@@ -83,6 +89,12 @@ public class Keychain
|
||||
@KeychainItem(key: "adiPb")
|
||||
public var adiPb: String?
|
||||
|
||||
// for some reason authenticated cert/session/team is completely not cached, which result in logging in for every request
|
||||
// we save it here so when user logs out we can clear cached account/session/team
|
||||
public var certificate: ALTCertificate? = nil
|
||||
public var session: ALTAppleAPISession? = nil
|
||||
public var team: ALTTeam? = nil
|
||||
|
||||
private init()
|
||||
{
|
||||
}
|
||||
@@ -91,7 +103,13 @@ public class Keychain
|
||||
{
|
||||
self.appleIDEmailAddress = nil
|
||||
self.appleIDPassword = nil
|
||||
self.appleIDAdsid = nil
|
||||
self.appleIDXcodeToken = nil
|
||||
self.signingCertificatePrivateKey = nil
|
||||
self.signingCertificateSerialNumber = nil
|
||||
|
||||
self.certificate = nil
|
||||
self.session = nil
|
||||
self.team = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,12 +30,16 @@ public extension UserDefaults
|
||||
@NSManaged var preferredServerID: String?
|
||||
|
||||
@NSManaged var isBackgroundRefreshEnabled: Bool
|
||||
@NSManaged var enableEMPforWireguard: Bool
|
||||
@NSManaged var isIdleTimeoutDisableEnabled: Bool
|
||||
@NSManaged var isAppLimitDisabled: Bool
|
||||
@NSManaged var isBetaUpdatesEnabled: Bool
|
||||
@NSManaged var customizeAppId: Bool
|
||||
@NSManaged var isExportResignedAppEnabled: Bool
|
||||
@NSManaged var isVerboseOperationsLoggingEnabled: Bool
|
||||
@NSManaged var isMinimuxerConsoleLoggingEnabled: Bool
|
||||
@NSManaged var isMinimuxerStatusCheckEnabled: Bool
|
||||
|
||||
@NSManaged var recreateDatabaseOnNextStart: Bool
|
||||
@NSManaged var isPairingReset: Bool
|
||||
@NSManaged var isDebugModeEnabled: Bool
|
||||
@@ -128,12 +132,15 @@ public extension UserDefaults
|
||||
let defaults = [
|
||||
#keyPath(UserDefaults.isAppLimitDisabled): false,
|
||||
#keyPath(UserDefaults.isBetaUpdatesEnabled): false,
|
||||
#keyPath(UserDefaults.customizeAppId): false,
|
||||
#keyPath(UserDefaults.isExportResignedAppEnabled): false,
|
||||
#keyPath(UserDefaults.isDebugModeEnabled): false,
|
||||
#keyPath(UserDefaults.isVerboseOperationsLoggingEnabled): false,
|
||||
#keyPath(UserDefaults.isMinimuxerConsoleLoggingEnabled): false, // minimuxer logging is disabled by default for console loggin
|
||||
#keyPath(UserDefaults.isMinimuxerStatusCheckEnabled): false, // minimuxer status check is disabled by default to support LocalDevVPN based cellular refresh
|
||||
#keyPath(UserDefaults.recreateDatabaseOnNextStart): false,
|
||||
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
||||
#keyPath(UserDefaults.enableEMPforWireguard): false,
|
||||
#keyPath(UserDefaults.isIdleTimeoutDisableEnabled): true,
|
||||
#keyPath(UserDefaults.isPairingReset): true,
|
||||
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
<attribute name="refreshedDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="resignedBundleIdentifier" attributeType="String"/>
|
||||
<attribute name="storeBuildVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="useMainProfile" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="version" attributeType="String"/>
|
||||
<relationship name="appExtensions" toMany="YES" deletionRule="Cascade" destinationEntity="InstalledExtension" inverseName="parentApp" inverseEntity="InstalledExtension"/>
|
||||
<relationship name="loggedErrors" toMany="YES" deletionRule="Nullify" destinationEntity="LoggedError" inverseName="installedApp" inverseEntity="LoggedError"/>
|
||||
|
||||