Compare commits

..

1 Commits

Author SHA1 Message Date
Zero King
4aca1dfa43 fix: typo in hasUpdate comparison
Signed-off-by: Zero King <l2dy@icloud.com>
2025-03-08 22:43:20 +05:30
156 changed files with 7042 additions and 5861 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -54,12 +54,57 @@ jobs:
swiftpm-cache-restore-keys: | swiftpm-cache-restore-keys: |
xcode-cache-sourcedata- 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 - name: List Files and derived data
run: | run: |
echo ">>>>>>>>> Workdir <<<<<<<<<<" echo ">>>>>>>>> Workdir <<<<<<<<<<"
ls -la . ls -la .
echo "" echo ""
echo ">>>>>>>>> Pods <<<<<<<<<<"
find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> SideStore <<<<<<<<<<" echo ">>>>>>>>> SideStore <<<<<<<<<<"
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo "" echo ""

View File

@@ -67,27 +67,26 @@ jobs:
bundle_id_suffix: ${{ inputs.bundle_id_suffix }} bundle_id_suffix: ${{ inputs.bundle_id_suffix }}
secrets: inherit secrets: inherit
# tests-build: tests-build:
# if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }} if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
# needs: shared needs: shared
# uses: ./.github/workflows/sidestore-tests-build.yml uses: ./.github/workflows/sidestore-tests-build.yml
# with: with:
# release_tag: ${{ inputs.release_tag }} release_tag: ${{ inputs.release_tag }}
# short_commit: ${{ needs.shared.outputs.short-commit }} short_commit: ${{ needs.shared.outputs.short-commit }}
# secrets: inherit secrets: inherit
# tests-run: tests-run:
# if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }} if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
# needs: [shared, tests-build] needs: [shared, tests-build]
# uses: ./.github/workflows/sidestore-tests-run.yml uses: ./.github/workflows/sidestore-tests-run.yml
# with: with:
# release_tag: ${{ inputs.release_tag }} release_tag: ${{ inputs.release_tag }}
# short_commit: ${{ needs.shared.outputs.short-commit }} short_commit: ${{ needs.shared.outputs.short-commit }}
# secrets: inherit secrets: inherit
deploy: 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') }} if: ${{ always() && (needs.tests-run.result == 'skipped' || needs.tests-run.result == 'success') }}
uses: ./.github/workflows/sidestore-deploy.yml uses: ./.github/workflows/sidestore-deploy.yml
with: with:

View File

@@ -35,8 +35,8 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- os: 'macos-26' - os: 'macos-15'
version: '26.0' version: '16.2'
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
outputs: outputs:
version: ${{ steps.version.outputs.version }} version: ${{ steps.version.outputs.version }}
@@ -173,6 +173,45 @@ jobs:
# swiftpm-cache-restore-keys: | # swiftpm-cache-restore-keys: |
# xcode-cache-sourcedata-build-${{ github.ref_name }}- # 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 - name: (Build) Clean previous build artifacts
# using 'tee' to intercept stdout and log for detailed build-log # using 'tee' to intercept stdout and log for detailed build-log
run: | run: |
@@ -188,6 +227,10 @@ jobs:
ls -la . ls -la .
echo "" echo ""
echo ">>>>>>>>> Pods <<<<<<<<<<"
find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> SideStore <<<<<<<<<<" echo ">>>>>>>>> SideStore <<<<<<<<<<"
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo "" echo ""

View File

@@ -116,49 +116,6 @@ jobs:
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
shell: bash 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 - name: Upload to releases
uses: IsaacShelton/update-existing-release@v1.3.1 uses: IsaacShelton/update-existing-release@v1.3.1
with: with:
@@ -166,21 +123,22 @@ jobs:
release: ${{ inputs.release_name }} release: ${{ inputs.release_name }}
tag: ${{ inputs.release_tag }} tag: ${{ inputs.release_tag }}
prerelease: ${{ inputs.is_beta }} prerelease: ${{ inputs.is_beta }}
files: ${{ steps.list_uploads.outputs.files }} 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
body: | 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!**
${{ steps.upstream_recommendation.outputs.content }} 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 }}).
## Build Info
Built at (UTC): `${{ steps.date.outputs.date }}` ## Build Info
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
Commit SHA: `${{ github.sha }}`
Version: `${{ inputs.version }}`
${{ steps.release_notes.outputs.content }} 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 }}
- name: Get formatted date - name: Get formatted date
run: | run: |
@@ -211,18 +169,8 @@ jobs:
- name: Set Release Info variables - name: Set Release Info variables
run: | run: |
echo "IS_BETA=${{ inputs.is_beta }}" >> $GITHUB_ENV
echo "BUNDLE_IDENTIFIER=${{ inputs.bundle_id }}" >> $GITHUB_ENV
echo "VERSION_IPA=${{ inputs.marketing_version }}" >> $GITHUB_ENV
echo "VERSION_DATE=$FORMATTED_DATE" >> $GITHUB_ENV
echo "RELEASE_CHANNEL=${{ inputs.release_channel }}" >> $GITHUB_ENV
echo "SIZE=$IPA_SIZE" >> $GITHUB_ENV
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 # Format localized description
get_description() { LOCALIZED_DESCRIPTION=$(cat <<EOF
cat <<EOF
This is release for: This is release for:
- version: "${{ inputs.version }}" - version: "${{ inputs.version }}"
- revision: "${{ inputs.short_commit }}" - revision: "${{ inputs.short_commit }}"
@@ -231,10 +179,16 @@ jobs:
Release Notes: Release Notes:
${{ steps.release_notes.outputs.content }} ${{ steps.release_notes.outputs.content }}
EOF EOF
} )
LOCALIZED_DESCRIPTION=$(get_description) echo "IS_BETA=${{ inputs.is_beta }}" >> $GITHUB_ENV
echo "$LOCALIZED_DESCRIPTION" echo "BUNDLE_IDENTIFIER=${{ inputs.bundle_id }}" >> $GITHUB_ENV
echo "VERSION_IPA=${{ inputs.marketing_version }}" >> $GITHUB_ENV
echo "VERSION_DATE=$FORMATTED_DATE" >> $GITHUB_ENV
echo "RELEASE_CHANNEL=${{ inputs.release_channel }}" >> $GITHUB_ENV
echo "SIZE=$IPA_SIZE" >> $GITHUB_ENV
echo "SHA256=$SHA256_HASH" >> $GITHUB_ENV
echo "DOWNLOAD_URL=https://github.com/SideStore/SideStore/releases/download/${{ inputs.release_tag }}/SideStore.ipa" >> $GITHUB_ENV
# multiline strings # multiline strings
echo "LOCALIZED_DESCRIPTION<<EOF" >> $GITHUB_ENV echo "LOCALIZED_DESCRIPTION<<EOF" >> $GITHUB_ENV

View File

@@ -19,8 +19,8 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- os: 'macos-26' - os: 'macos-15'
version: '26.0' version: '16.2'
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
@@ -37,7 +37,7 @@ jobs:
- name: Setup Xcode - name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0 uses: maxim-lobanov/setup-xcode@v1.6.0
with: with:
xcode-version: '26.0' xcode-version: '16.2'
# - name: (Tests-Build) Cache Build # - name: (Tests-Build) Cache Build
# uses: irgaly/xcode-cache@v1.8.1 # uses: irgaly/xcode-cache@v1.8.1
@@ -68,6 +68,41 @@ jobs:
~/Library/Caches/org.swift.swiftpm ~/Library/Caches/org.swift.swiftpm
key: xcode-cache-tests-${{ github.ref_name }}- 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) - name: Clean Derived Data (if required)
if: ${{ vars.PERFORM_CLEAN_TESTS_BUILD == '1' }} if: ${{ vars.PERFORM_CLEAN_TESTS_BUILD == '1' }}
run: | run: |
@@ -89,6 +124,10 @@ jobs:
ls -la . ls -la .
echo "" echo ""
echo ">>>>>>>>> Pods <<<<<<<<<<"
find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> SideStore <<<<<<<<<<" echo ">>>>>>>>> SideStore <<<<<<<<<<"
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo "" echo ""

View File

@@ -19,8 +19,8 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- os: 'macos-26' - os: 'macos-15'
version: '26.0' version: '16.2'
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
@@ -38,7 +38,7 @@ jobs:
- name: Setup Xcode - name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0 uses: maxim-lobanov/setup-xcode@v1.6.0
with: with:
xcode-version: '26.0' xcode-version: '16.2'
# - name: (Tests-Run) Cache Build # - name: (Tests-Run) Cache Build
# uses: irgaly/xcode-cache@v1.8.1 # uses: irgaly/xcode-cache@v1.8.1
@@ -56,6 +56,41 @@ jobs:
~/Library/Caches/org.swift.swiftpm ~/Library/Caches/org.swift.swiftpm
key: xcode-cache-tests-${{ github.ref_name }}-${{ github.sha }} 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 - name: (Tests-Run) Clean previous build artifacts
run: | run: |
make clean make clean
@@ -69,6 +104,10 @@ jobs:
ls -la . ls -la .
echo "" echo ""
echo ">>>>>>>>> Pods <<<<<<<<<<"
find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> SideStore <<<<<<<<<<" echo ">>>>>>>>> SideStore <<<<<<<<<<"
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo "" echo ""

View File

@@ -12,8 +12,8 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- os: 'macos-26' - os: 'macos-15'
version: '26.0' version: '16.2'
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
@@ -89,6 +89,43 @@ jobs:
~/Library/Caches/org.swift.swiftpm ~/Library/Caches/org.swift.swiftpm
key: xcode-cache-build-stable- 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 - name: (Build) Clean previous build artifacts
run: | run: |
make clean make clean
@@ -103,6 +140,10 @@ jobs:
ls -la . ls -la .
echo "" echo ""
echo ">>>>>>>>> Pods <<<<<<<<<<"
find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo ""
echo ">>>>>>>>> SideStore <<<<<<<<<<" echo ">>>>>>>>> SideStore <<<<<<<<<<"
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
echo "" echo ""

6
.gitmodules vendored
View File

@@ -30,7 +30,7 @@
url = https://github.com/rileytestut/Roxas.git url = https://github.com/rileytestut/Roxas.git
[submodule "Dependencies/libimobiledevice"] [submodule "Dependencies/libimobiledevice"]
path = Dependencies/libimobiledevice path = Dependencies/libimobiledevice
url = https://github.com/SideStore/libimobiledevice url = https://github.com/libimobiledevice/libimobiledevice
[submodule "Dependencies/libusbmuxd"] [submodule "Dependencies/libusbmuxd"]
path = Dependencies/libusbmuxd path = Dependencies/libusbmuxd
url = https://github.com/libimobiledevice/libusbmuxd.git url = https://github.com/libimobiledevice/libusbmuxd.git
@@ -51,8 +51,8 @@
url = https://github.com/SideStore/minimuxer url = https://github.com/SideStore/minimuxer
branch = master branch = master
[submodule "SideStore/em_proxy"] [submodule "SideStore/em_proxy"]
path = SideStore/em_proxy path = SideStore/em_proxy
url = https://github.com/SideStore/em_proxy url = https://github.com/SideStore/em_proxy
branch = master branch = master
[submodule "SideStore/libfragmentzip"] [submodule "SideStore/libfragmentzip"]
path = SideStore/libfragmentzip path = SideStore/libfragmentzip

View File

@@ -4,7 +4,7 @@
<dict> <dict>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.$(GROUP_ID)</string> <string>group.$(APP_GROUP_IDENTIFIER)</string>
</array> </array>
</dict> </dict>
</plist> </plist>

File diff suppressed because it is too large Load Diff

View File

@@ -10,4 +10,7 @@
<FileRef <FileRef
location = "group:Dependencies/Roxas/Roxas.xcodeproj"> location = "group:Dependencies/Roxas/Roxas.xcodeproj">
</FileRef> </FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace> </Workspace>

View File

@@ -2,14 +2,24 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>aps-environment</key> <!-- <key>com.apple.security.files.user-selected.read-write</key>
<string>development</string> <array>
<string></string>
</array>
<key>com.apple.developer.applesignin</key>
<array>
<string></string>
</array> -->
<key>com.apple.developer.kernel.extended-virtual-addressing</key> <key>com.apple.developer.kernel.extended-virtual-addressing</key>
<true/> <true/>
<key>com.apple.developer.kernel.increased-debugging-memory-limit</key> <key>com.apple.developer.kernel.increased-debugging-memory-limit</key>
<true/> <true/>
<key>com.apple.developer.kernel.increased-memory-limit</key> <key>com.apple.developer.kernel.increased-memory-limit</key>
<true/> <true/>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.siri</key>
<true/>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.$(APP_GROUP_IDENTIFIER)</string> <string>group.$(APP_GROUP_IDENTIFIER)</string>

View File

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

View File

@@ -0,0 +1,115 @@
//
// 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)
}
}

View File

@@ -27,12 +27,10 @@ extension AppDelegate
static let addSourceDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".AddSourceDeepLinkNotification") static let addSourceDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".AddSourceDeepLinkNotification")
static let appBackupDidFinish = Notification.Name(Bundle.Info.appbundleIdentifier + ".AppBackupDidFinish") static let appBackupDidFinish = Notification.Name(Bundle.Info.appbundleIdentifier + ".AppBackupDidFinish")
static let exportCertificateNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".ExportCertificateNotification")
static let importAppDeepLinkURLKey = "fileURL" static let importAppDeepLinkURLKey = "fileURL"
static let appBackupResultKey = "result" static let appBackupResultKey = "result"
static let addSourceDeepLinkURLKey = "sourceURL" static let addSourceDeepLinkURLKey = "sourceURL"
static let exportCertificateCallbackTemplateKey = "callback"
} }
@UIApplicationMain @UIApplicationMain
@@ -93,14 +91,13 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
} }
} }
self.setTintColor() AnalyticsManager.shared.start()
self.setTintColor() self.setTintColor()
self.prepareImageCache() self.prepareImageCache()
// TODO: @mahee96: find if we need to start em_proxy as in altstore? // TODO: @mahee96: find if we need to start em_proxy as in altstore?
if UserDefaults.standard.enableEMPforWireguard { // start_em_proxy(bind_addr: Consts.Proxy.serverURL)
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
}
SecureValueTransformer.register() SecureValueTransformer.register()
@@ -125,9 +122,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
{ {
// Make sure to update SceneDelegate.sceneDidEnterBackground() as well. // Make sure to update SceneDelegate.sceneDidEnterBackground() as well.
// TODO: @mahee96: find if we need to stop em_proxy as in altstore? // TODO: @mahee96: find if we need to stop em_proxy as in altstore?
if UserDefaults.standard.enableEMPforWireguard { // stop_em_proxy()
stop_em_proxy()
}
guard let oneMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date()) else { return } guard let oneMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date()) else { return }
let midnightOneMonthAgo = Calendar.current.startOfDay(for: oneMonthAgo) let midnightOneMonthAgo = Calendar.current.startOfDay(for: oneMonthAgo)
@@ -144,9 +139,9 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationWillEnterForeground(_ application: UIApplication) func applicationWillEnterForeground(_ application: UIApplication)
{ {
AppManager.shared.update() AppManager.shared.update()
if UserDefaults.standard.enableEMPforWireguard { start_em_proxy(bind_addr: Consts.Proxy.serverURL)
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
} PatreonAPI.shared.refreshPatreonAccount()
} }
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
@@ -297,26 +292,6 @@ private extension AppDelegate
return true 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 default: return false
} }
} }

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/> <device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
<capability name="Named colors" minToolsVersion="9.0"/> <capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@@ -14,7 +14,7 @@
<objects> <objects>
<navigationController storyboardIdentifier="navigationController" id="ZTo-53-dSL" sceneMemberID="viewController"> <navigationController storyboardIdentifier="navigationController" id="ZTo-53-dSL" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Aej-RF-PfV" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target"> <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Aej-RF-PfV" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="20" width="375" height="96"/> <rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="barTintColor" name="SettingsBackground"/> <color key="barTintColor" name="SettingsBackground"/>
@@ -42,13 +42,13 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oyW-Fd-ojD" userLabel="Sizing View"> <view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oyW-Fd-ojD" userLabel="Sizing View">
<rect key="frame" x="0.0" y="64" width="375" height="603"/> <rect key="frame" x="0.0" y="44" width="375" height="623"/>
</view> </view>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" alwaysBounceVertical="YES" indicatorStyle="white" keyboardDismissMode="onDrag" translatesAutoresizingMaskIntoConstraints="NO" id="WXx-hX-AXv"> <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" alwaysBounceVertical="YES" indicatorStyle="white" keyboardDismissMode="onDrag" translatesAutoresizingMaskIntoConstraints="NO" id="WXx-hX-AXv">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<subviews> <subviews>
<view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2wp-qG-f0Z"> <view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2wp-qG-f0Z">
<rect key="frame" x="0.0" y="0.0" width="375" height="603"/> <rect key="frame" x="0.0" y="0.0" width="375" height="623"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" spacing="50" translatesAutoresizingMaskIntoConstraints="NO" id="YmX-7v-pxh"> <stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" spacing="50" translatesAutoresizingMaskIntoConstraints="NO" id="YmX-7v-pxh">
<rect key="frame" x="16" y="6" width="343" height="359.5"/> <rect key="frame" x="16" y="6" width="343" height="359.5"/>
@@ -57,13 +57,13 @@
<rect key="frame" x="0.0" y="0.0" width="343" height="67.5"/> <rect key="frame" x="0.0" y="0.0" width="343" height="67.5"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Welcome to SideStore." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="EI2-V3-zQZ"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Welcome to SideStore." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="EI2-V3-zQZ">
<rect key="frame" x="0.0" y="0.0" width="343" height="41"/> <rect key="frame" x="0.0" y="0.0" width="333.5" height="41"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="34"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </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"> <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="306.5" height="20.5"/> <rect key="frame" x="0.0" y="47" width="308.5" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -160,7 +160,7 @@
</stackView> </stackView>
</subviews> </subviews>
</stackView> </stackView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2N5-zd-fUj"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2N5-zd-fUj">
<rect key="frame" x="0.0" y="191" width="343" height="51"/> <rect key="frame" x="0.0" y="191" width="343" height="51"/>
<color key="backgroundColor" name="SettingsHighlighted"/> <color key="backgroundColor" name="SettingsHighlighted"/>
<constraints> <constraints>
@@ -179,7 +179,7 @@
</subviews> </subviews>
</stackView> </stackView>
<stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="250" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="DBk-rT-ZE8"> <stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="250" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="DBk-rT-ZE8">
<rect key="frame" x="16" y="498.5" width="343" height="96.5"/> <rect key="frame" x="16" y="518.5" width="343" height="96.5"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Why do we need this?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p9U-0q-Kn8"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Why do we need this?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p9U-0q-Kn8">
<rect key="frame" x="0.0" y="0.0" width="343" height="20.5"/> <rect key="frame" x="0.0" y="0.0" width="343" height="20.5"/>
@@ -198,10 +198,6 @@
</stackView> </stackView>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstItem="DBk-rT-ZE8" firstAttribute="leading" secondItem="2wp-qG-f0Z" secondAttribute="leadingMargin" id="5AT-nV-ZP9"/>
<constraint firstAttribute="bottomMargin" secondItem="DBk-rT-ZE8" secondAttribute="bottom" id="HgY-oY-8KM"/>
<constraint firstAttribute="trailingMargin" secondItem="DBk-rT-ZE8" secondAttribute="trailing" id="VCf-bW-2K4"/>
<constraint firstItem="YmX-7v-pxh" firstAttribute="top" secondItem="2wp-qG-f0Z" secondAttribute="top" constant="6" id="iUr-Nd-tkt"/>
<constraint firstItem="DBk-rT-ZE8" firstAttribute="top" relation="greaterThanOrEqual" secondItem="YmX-7v-pxh" secondAttribute="bottom" constant="8" symbolic="YES" id="zTU-eY-DWd"/> <constraint firstItem="DBk-rT-ZE8" firstAttribute="top" relation="greaterThanOrEqual" secondItem="YmX-7v-pxh" secondAttribute="bottom" constant="8" symbolic="YES" id="zTU-eY-DWd"/>
</constraints> </constraints>
</view> </view>
@@ -219,15 +215,19 @@
<constraints> <constraints>
<constraint firstAttribute="bottom" secondItem="WXx-hX-AXv" secondAttribute="bottom" id="0jL-Ky-ju6"/> <constraint firstAttribute="bottom" secondItem="WXx-hX-AXv" secondAttribute="bottom" id="0jL-Ky-ju6"/>
<constraint firstAttribute="leadingMargin" secondItem="YmX-7v-pxh" secondAttribute="leading" id="2PO-lG-dmB"/> <constraint firstAttribute="leadingMargin" secondItem="YmX-7v-pxh" secondAttribute="leading" id="2PO-lG-dmB"/>
<constraint firstItem="DBk-rT-ZE8" firstAttribute="leading" secondItem="2wp-qG-f0Z" secondAttribute="leadingMargin" id="5AT-nV-ZP9"/>
<constraint firstItem="oyW-Fd-ojD" firstAttribute="top" secondItem="zMn-DV-fpy" secondAttribute="top" id="730-db-ukB"/> <constraint firstItem="oyW-Fd-ojD" firstAttribute="top" secondItem="zMn-DV-fpy" secondAttribute="top" id="730-db-ukB"/>
<constraint firstItem="2wp-qG-f0Z" firstAttribute="bottomMargin" secondItem="DBk-rT-ZE8" secondAttribute="bottom" id="HgY-oY-8KM"/>
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="oyW-Fd-ojD" secondAttribute="trailing" id="KGE-CN-SWf"/> <constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="oyW-Fd-ojD" secondAttribute="trailing" id="KGE-CN-SWf"/>
<constraint firstItem="WXx-hX-AXv" firstAttribute="top" secondItem="mjy-4S-hyH" secondAttribute="top" id="LPQ-bF-ic0"/> <constraint firstItem="WXx-hX-AXv" firstAttribute="top" secondItem="mjy-4S-hyH" secondAttribute="top" id="LPQ-bF-ic0"/>
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="WXx-hX-AXv" secondAttribute="trailing" id="MG7-A6-pKp"/> <constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="WXx-hX-AXv" secondAttribute="trailing" id="MG7-A6-pKp"/>
<constraint firstAttribute="trailingMargin" secondItem="YmX-7v-pxh" secondAttribute="trailing" id="O4T-nu-o3e"/> <constraint firstAttribute="trailingMargin" secondItem="YmX-7v-pxh" secondAttribute="trailing" id="O4T-nu-o3e"/>
<constraint firstItem="zMn-DV-fpy" firstAttribute="bottom" secondItem="oyW-Fd-ojD" secondAttribute="bottom" id="PuX-ab-cEq"/> <constraint firstItem="zMn-DV-fpy" firstAttribute="bottom" secondItem="oyW-Fd-ojD" secondAttribute="bottom" id="PuX-ab-cEq"/>
<constraint firstItem="oyW-Fd-ojD" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="SzC-gC-Nvi"/> <constraint firstItem="oyW-Fd-ojD" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="SzC-gC-Nvi"/>
<constraint firstItem="2wp-qG-f0Z" firstAttribute="trailingMargin" secondItem="DBk-rT-ZE8" secondAttribute="trailing" id="VCf-bW-2K4"/>
<constraint firstItem="WXx-hX-AXv" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="d08-zF-5X6"/> <constraint firstItem="WXx-hX-AXv" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="d08-zF-5X6"/>
<constraint firstItem="2wp-qG-f0Z" firstAttribute="height" secondItem="oyW-Fd-ojD" secondAttribute="height" id="dFN-pw-TWt"/> <constraint firstItem="2wp-qG-f0Z" firstAttribute="height" secondItem="oyW-Fd-ojD" secondAttribute="height" id="dFN-pw-TWt"/>
<constraint firstItem="YmX-7v-pxh" firstAttribute="top" secondItem="2wp-qG-f0Z" secondAttribute="top" constant="6" id="iUr-Nd-tkt"/>
<constraint firstItem="2wp-qG-f0Z" firstAttribute="width" secondItem="oyW-Fd-ojD" secondAttribute="width" id="rYO-GN-0Lk"/> <constraint firstItem="2wp-qG-f0Z" firstAttribute="width" secondItem="oyW-Fd-ojD" secondAttribute="width" id="rYO-GN-0Lk"/>
</constraints> </constraints>
</view> </view>
@@ -264,7 +264,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="bp6-55-IG2"> <stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="bp6-55-IG2">
<rect key="frame" x="0.0" y="64" width="375" height="544"/> <rect key="frame" x="0.0" y="44" width="375" height="564"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="FjP-tm-w7K"> <stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="FjP-tm-w7K">
<rect key="frame" x="16" y="35" width="343" height="95.5"/> <rect key="frame" x="16" y="35" width="343" height="95.5"/>
@@ -298,7 +298,7 @@
</subviews> </subviews>
</stackView> </stackView>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="LpI-Jt-SzX"> <stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="LpI-Jt-SzX">
<rect key="frame" x="16" y="161" width="343" height="95.5"/> <rect key="frame" x="16" y="168" width="343" height="95.5"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0LW-eE-qHa"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0LW-eE-qHa">
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/> <rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
@@ -310,7 +310,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="dMu-eg-gIO"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="dMu-eg-gIO">
<rect key="frame" x="79" y="16" width="264" height="64"/> <rect key="frame" x="79" y="15.5" width="264" height="64"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Connect to Wi-Fi and VPN" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="esj-pD-D4A"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Connect to Wi-Fi and VPN" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="esj-pD-D4A">
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/> <rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
@@ -318,7 +318,7 @@
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<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"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable SideStore VPN in Wireguard and be able to use Sidestore on the go." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="4rk-ge-FSj">
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/> <rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/> <fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -329,7 +329,7 @@
</subviews> </subviews>
</stackView> </stackView>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="tfb-ja-9UC"> <stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="tfb-ja-9UC">
<rect key="frame" x="16" y="287.5" width="343" height="95.5"/> <rect key="frame" x="16" y="300.5" width="343" height="95.5"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nVr-El-Csi"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nVr-El-Csi">
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/> <rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
@@ -341,7 +341,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="z6Y-zi-teL"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="z6Y-zi-teL">
<rect key="frame" x="79" y="15.5" width="264" height="64"/> <rect key="frame" x="79" y="16" width="264" height="64"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Download Apps" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="JeJ-bk-UCA"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Download Apps" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="JeJ-bk-UCA">
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/> <rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
@@ -360,7 +360,7 @@
</subviews> </subviews>
</stackView> </stackView>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="X3r-G1-vf2"> <stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="X3r-G1-vf2">
<rect key="frame" x="16" y="413.5" width="343" height="95.5"/> <rect key="frame" x="16" y="433.5" width="343" height="95.5"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="4" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="i2U-NL-plG"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="4" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="i2U-NL-plG">
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/> <rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
@@ -372,7 +372,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Xs6-pJ-PUz"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Xs6-pJ-PUz">
<rect key="frame" x="79" y="17" width="264" height="62"/> <rect key="frame" x="79" y="16" width="264" height="64"/>
<subviews> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
@@ -381,7 +381,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </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"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apps are refreshed in the background while you are on SideStore VPN!" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="HU5-Hv-E3d">
<rect key="frame" x="0.0" y="25.5" width="264" height="36.5"/> <rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/> <fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -431,7 +431,7 @@
</objects> </objects>
<point key="canvasLocation" x="1353" y="736"/> <point key="canvasLocation" x="1353" y="736"/>
</scene> </scene>
<!--Refresh SideStore--> <!--Refresh AltStore-->
<scene sceneID="9Vh-dM-OqX"> <scene sceneID="9Vh-dM-OqX">
<objects> <objects>
<viewController storyboardIdentifier="refreshAltStoreViewController" id="aoK-yE-UVT" customClass="RefreshAltStoreViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController"> <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"/> <constraint firstItem="tDQ-ao-1Jg" firstAttribute="leading" secondItem="R83-kV-365" secondAttribute="leadingMargin" id="zEt-Xr-kJx"/>
</constraints> </constraints>
</view> </view>
<navigationItem key="navigationItem" title="Refresh SideStore" largeTitleDisplayMode="always" id="5nk-NR-jtV"/> <navigationItem key="navigationItem" title="Refresh AltStore" largeTitleDisplayMode="always" id="5nk-NR-jtV"/>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/> <simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
<connections> <connections>
<outlet property="placeholderView" destination="fpO-Bf-gFY" id="q7d-au-d94"/> <outlet property="placeholderView" destination="fpO-Bf-gFY" id="q7d-au-d94"/>

View File

@@ -532,7 +532,6 @@
<rect key="frame" x="0.0" y="20" width="375" height="96"/> <rect key="frame" x="0.0" y="20" width="375" height="96"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<color key="tintColor" name="Primary"/> <color key="tintColor" name="Primary"/>
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
<connections> <connections>
@@ -562,7 +561,6 @@
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="CzO-Kt-BlZ" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target"> <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="CzO-Kt-BlZ" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="20" width="375" height="96"/> <rect key="frame" x="0.0" y="20" width="375" height="96"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
<connections> <connections>
@@ -915,7 +913,6 @@
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="9sB-f3-Fnk"> <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="9sB-f3-Fnk">
<rect key="frame" x="0.0" y="0.0" width="375" height="108"/> <rect key="frame" x="0.0" y="0.0" width="375" height="108"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
<connections> <connections>

View File

@@ -12,73 +12,84 @@ import EmotionalDamage
import minimuxer import minimuxer
import WidgetKit import WidgetKit
import AltSign
import AltStoreCore import AltStoreCore
import UniformTypeIdentifiers import UniformTypeIdentifiers
let pairingFileName = "ALTPairingFile.mobiledevicepairing" let pairingFileName = "ALTPairingFile.mobiledevicepairing"
final class LaunchViewController: UIViewController, UIDocumentPickerDelegate { final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
{
private var didFinishLaunching = false private var didFinishLaunching = false
private var retries = 0
private var maxRetries = 3
private var splashView: SplashView!
private var destinationViewController: TabBarController?
private var startTime: Date!
override func viewDidLoad() { private var destinationViewController: TabBarController!
override var launchConditions: [RSTLaunchCondition] {
let isDatabaseStarted = RSTLaunchCondition(condition: { DatabaseManager.shared.isStarted }) { (completionHandler) in
DatabaseManager.shared.start(completionHandler: completionHandler)
}
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
}
super.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) { override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(true)
guard !didFinishLaunching else { return } if #available(iOS 17, *), !UserDefaults.standard.sidejitenable {
Task { DispatchQueue.global().async {
startTime = Date() self.isSideJITServerDetected() { result in
await runLaunchSequence() DispatchQueue.main.async {
doPostLaunch() switch result {
} case .success():
} let dialogMessage = UIAlertController(title: "SideJITServer Detected", message: "Would you like to enable SideJITServer", preferredStyle: .alert)
private func runLaunchSequence() async { // Create OK button with action handler
guard retries < maxRetries else { return } let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
retries += 1 UserDefaults.standard.sidejitenable = true
})
await Task.detached { let cancel = UIAlertAction(title: "Cancel", style: .cancel)
if !DatabaseManager.shared.isStarted { //Add OK button to a dialog message
await withCheckedContinuation { continuation in dialogMessage.addAction(ok)
DatabaseManager.shared.start { error in dialogMessage.addAction(cancel)
if let error {
Task { await self.handleLaunchError(error, retryCallback: self.runLaunchSequence) } // Present Alert to
} else { self.present(dialogMessage, animated: true, completion: nil)
Task { await self.finishLaunching() } case .failure(_):
print("Cannot find sideJITServer")
} }
continuation.resume(returning: ())
} }
} }
} else {
await self.finishLaunching()
} }
}.value }
}
private func doPostLaunch() {
SideJITManager.shared.checkAndPromptIfNeeded(presentingVC: self)
if #available(iOS 17, *), UserDefaults.standard.sidejitenable { if #available(iOS 17, *), UserDefaults.standard.sidejitenable {
DispatchQueue.global().async { SideJITManager.shared.askForNetwork() } DispatchQueue.global().async {
self.askfornetwork()
}
print("SideJITServer Enabled") print("SideJITServer Enabled")
} }
#if !targetEnvironment(simulator) #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 { guard let pf = fetchPairingFile() else {
displayError("Device pairing file not found.") displayError("Device pairing file not found.")
return return
@@ -87,48 +98,145 @@ final class LaunchViewController: UIViewController, UIDocumentPickerDelegate {
#endif #endif
} }
func start_minimuxer_threads(_ pairing_file: String) { func askfornetwork() {
target_minimuxer_address() let address = UserDefaults.standard.textInputSideJITServerurl ?? ""
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
do { var SJSURL = address
let loggingEnabled = UserDefaults.standard.isMinimuxerConsoleLoggingEnabled
try minimuxer.startWithLogger(pairing_file, documentsDirectory, loggingEnabled) if (UserDefaults.standard.textInputSideJITServerurl ?? "").isEmpty {
} catch { SJSURL = "http://sidejitserver._http._tcp.local:8080"
try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent(pairingFileName))
displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR")")
} }
start_auto_mounter(documentsDirectory)
// 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 fetchPairingFile() -> String? { PairingFileManager.shared.fetchPairingFile(presentingVC: self) } 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) { func displayError(_ msg: String) {
print(msg) print(msg)
let alert = UIAlertController(title: "Error launching SideStore", message: msg, preferredStyle: .alert) // Create a new alert
self.present(alert, animated: true) 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]) { func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
let url = urls[0] let url = urls[0]
let isSecuredURL = url.startAccessingSecurityScopedResource() == true let isSecuredURL = url.startAccessingSecurityScopedResource() == true
defer {
if (isSecuredURL) {
url.stopAccessingSecurityScopedResource()
}
}
do { do {
let data = try Data(contentsOf: url) // Read to a string
guard let pairingString = String(data: data, encoding: .utf8) else { 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") displayError("Unable to read pairing file")
return
} }
try pairingString.write(to: FileManager.default.documentsDirectory.appendingPathComponent(pairingFileName), atomically: true, encoding: .utf8)
start_minimuxer_threads(pairingString) // 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 { } catch {
displayError("Unable to read pairing file") displayError("Unable to read pairing file")
} }
if (isSecuredURL) {
url.stopAccessingSecurityScopedResource()
}
controller.dismiss(animated: true, completion: nil) controller.dismiss(animated: true, completion: nil)
} }
@@ -136,74 +244,75 @@ final class LaunchViewController: UIViewController, UIDocumentPickerDelegate {
displayError("Choosing a pairing file was cancelled. Please re-open the app and try again.") displayError("Choosing a pairing file was cancelled. Please re-open the app and try again.")
} }
func importAccountAtFile(_ file: URL, remove: Bool = false) { func start_minimuxer_threads(_ pairing_file: String) {
_ = file.startAccessingSecurityScopedResource() target_minimuxer_address()
defer { file.stopAccessingSecurityScopedResource() } let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
guard let accountD = try? Data(contentsOf: file) else { do {
return Logger.main.notice("Could not parse data from file \(file)") // enable minimuxer console logging only if enabled in settings
let isMinimuxerConsoleLoggingEnabled = UserDefaults.standard.isMinimuxerConsoleLoggingEnabled
try minimuxer.startWithLogger(pairing_file, documentsDirectory, isMinimuxerConsoleLoggingEnabled)
} 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!")")
} }
guard let account = try? Foundation.JSONDecoder().decode(ImportedAccount.self, from: accountD) else { if #available(iOS 17, *) {
return Logger.main.notice("Could not parse data from file \(file)") // TODO: iOS 17 and above have a new JIT implementation that is completely broken in SideStore :(
} }
print("We want to import this account probably: \(account)") else {
if remove { start_auto_mounter(documentsDirectory)
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)
}
}
func detectAndImportAccountFile() { // Create destinationViewController now so view controllers can register for receiving Notifications.
let accountFileURL = FileManager.default.documentsDirectory.appendingPathComponent("Account.sideconf") self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as? TabBarController
#if !DEBUG
importAccountAtFile(accountFileURL, remove: true)
#else
importAccountAtFile(accountFileURL)
#endif
} }
} }
extension LaunchViewController { extension LaunchViewController
@MainActor {
func handleLaunchError(_ error: Error, retryCallback: (() async -> Void)? = nil) { override func handleLaunchError(_ error: Error)
do { throw error } catch let error as NSError { {
do
{
throw error
}
catch let error as NSError
{
let title = error.userInfo[NSLocalizedFailureErrorKey] as? String ?? NSLocalizedString("Unable to Launch SideStore", comment: "") let title = error.userInfo[NSLocalizedFailureErrorKey] as? String ?? NSLocalizedString("Unable to Launch SideStore", comment: "")
let desc: String
if #available(iOS 14.5, *) { let errorDescription: String
desc = ([error.debugDescription] + error.underlyingErrors.map { ($0 as NSError).debugDescription }).joined(separator: "\n\n")
} else { if #available(iOS 14.5, *)
desc = error.debugDescription {
let errorMessages = [error.debugDescription] + error.underlyingErrors.map { ($0 as NSError).debugDescription }
errorDescription = errorMessages.joined(separator: "\n\n")
} }
let alert = UIAlertController(title: title, message: desc, preferredStyle: .alert) else
alert.addAction(UIAlertAction(title: NSLocalizedString("Retry", comment: ""), style: .default) { _ in {
Task { await retryCallback?() } errorDescription = error.debugDescription
}) }
present(alert, animated: true)
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)
} }
} }
@MainActor override func finishLaunching()
func finishLaunching() async { {
guard !didFinishLaunching else { return } super.finishLaunching()
didFinishLaunching = true
guard !self.didFinishLaunching else { return }
AppManager.shared.update() AppManager.shared.update()
AppManager.shared.updatePatronsIfNeeded()
PatreonAPI.shared.refreshPatreonAccount()
AppManager.shared.updateAllSources { result in AppManager.shared.updateAllSources { result in
guard case .failure(let error) = result else { return } guard case .failure(let error) = result else { return }
Logger.main.error("Failed to update sources on launch. \(error.localizedDescription, privacy: .public)") Logger.main.error("Failed to update sources on launch. \(error.localizedDescription, privacy: .public)")
let errorDesc = ErrorProcessing(.fullError).getDescription(error: error as NSError) let errorDesc = ErrorProcessing(.fullError).getDescription(error: error as NSError)
print("Failed to update sources on launch. \(errorDesc)") print("Failed to update sources on launch. \(errorDesc)")
@@ -211,64 +320,63 @@ extension LaunchViewController {
if String(describing: error).contains("The Internet connection appears to be offline"){ if String(describing: error).contains("The Internet connection appears to be offline"){
mode = .localizedDescription // dont make noise! mode = .localizedDescription // dont make noise!
} }
let toastView = ToastView(error: error, mode: mode) let toastView = ToastView(error: error, mode: mode)
toastView.addTarget(self.destinationViewController, action: #selector(TabBarController.presentSources), for: .touchUpInside) 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)
} }
updateKnownSources()
self.updateKnownSources()
// Ask widgets to be refreshed
WidgetCenter.shared.reloadAllTimelines() WidgetCenter.shared.reloadAllTimelines()
didFinishLaunching = true
let destinationVC = destinationViewController! // 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 elapsed = abs(startTime.timeIntervalSinceNow) UIView.animate(withDuration: 0.2) {
let remaining = elapsed >= 1 ? 0 : 1 - elapsed self.destinationViewController.view.alpha = 1.0
try? await Task.sleep(nanoseconds: UInt64(remaining * 1_000_000_000))
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)
])
// 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() { self.didFinishLaunching = true
}
}
private extension LaunchViewController
{
func updateKnownSources()
{
AppManager.shared.updateKnownSources { result in AppManager.shared.updateKnownSources { result in
switch result { switch result
{
case .failure(let error): print("[ALTLog] Failed to update known sources:", error) case .failure(let error): print("[ALTLog] Failed to update known sources:", error)
case .success((_, let blockedSources)): case .success((_, let blockedSources)):
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
let blockedSourceIDs = Set(blockedSources.lazy.map { $0.identifier }) let blockedSourceIDs = Set(blockedSources.lazy.map { $0.identifier })
let blockedSourceURLs = Set(blockedSources.lazy.compactMap { $0.sourceURL }) 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 predicate = NSPredicate(format: "%K IN %@ OR %K IN %@",
let blocked = blockedSources.first { $0.identifier == source.identifier } #keyPath(Source.identifier), blockedSourceIDs,
return SourceError.blocked(source, bundleIDs: blocked?.bundleIDs, existingSource: source) #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)
} }
guard !sourceErrors.isEmpty else { return } guard !sourceErrors.isEmpty else { return }
Task { Task {
for error in sourceErrors { for error in sourceErrors
{
let title = String(format: NSLocalizedString("“%@” Blocked", comment: ""), error.$source.name) let title = String(format: NSLocalizedString("“%@” Blocked", comment: ""), error.$source.name)
let message = [error.localizedDescription, error.recoverySuggestion].compactMap { $0 }.joined(separator: "\n\n") let message = [error.localizedDescription, error.recoverySuggestion].compactMap { $0 }.joined(separator: "\n\n")
await self.presentAlert(title: title, message: message) await self.presentAlert(title: title, message: message)
} }
} }
@@ -277,142 +385,3 @@ 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()
}
}

View File

@@ -8,6 +8,7 @@
import Foundation import Foundation
import UIKit import UIKit
import SwiftUI
import UserNotifications import UserNotifications
import MobileCoreServices import MobileCoreServices
import Intents import Intents
@@ -22,6 +23,7 @@ import Roxas
extension AppManager extension AppManager
{ {
static let didFetchSourceNotification = Notification.Name("io.sidestore.AppManager.didFetchSource") 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 didAddSourceNotification = Notification.Name("io.sidestore.AppManager.didAddSource")
static let didRemoveSourceNotification = Notification.Name("io.sidestore.AppManager.didRemoveSource") static let didRemoveSourceNotification = Notification.Name("io.sidestore.AppManager.didRemoveSource")
static let willInstallAppFromNewSourceNotification = Notification.Name("io.sidestore.AppManager.willInstallAppFromNewSource") static let willInstallAppFromNewSourceNotification = Notification.Name("io.sidestore.AppManager.willInstallAppFromNewSource")
@@ -589,6 +591,34 @@ extension AppManager
return updateKnownSourcesOperation 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) func updateAllSources(completion: @escaping (Result<Void, Error>) -> Void)
{ {
self.updateSourcesResult = nil self.updateSourcesResult = nil
@@ -675,37 +705,9 @@ 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 return group
} }
@@ -730,11 +732,10 @@ extension AppManager
} }
} }
assert(appVersion as AnyObject !== installedApp) // Make sure we never accidentally "update" to already installed app. let operation = AppOperation.update(appVersion)
assert(operation.app as AnyObject !== installedApp) // Make sure we never accidentally "update" to already installed app.
Task{ self.perform([operation], presentingViewController: presentingViewController, group: group)
await self.perform([.update(appVersion)], presentingViewController: presentingViewController, group: group)
}
return group.progress return group.progress
} }
@@ -744,20 +745,16 @@ extension AppManager
{ {
let group = group ?? RefreshGroup() let group = group ?? RefreshGroup()
Task{ let operations = installedApps.map { AppOperation.refresh($0) }
await self.perform(installedApps.map { .refresh($0) }, presentingViewController: presentingViewController, group: group) return self.perform(operations, presentingViewController: presentingViewController, group: group)
}
return group
} }
func activate(_ installedApp: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) func activate(_ installedApp: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void)
{ {
let group = RefreshGroup() let group = RefreshGroup()
Task{ let operation = AppOperation.activate(installedApp)
await self.perform([.activate(installedApp)], presentingViewController: presentingViewController, group: group) self.perform([operation], presentingViewController: presentingViewController, group: group)
}
group.completionHandler = { (results) in group.completionHandler = { (results) in
do do
@@ -815,9 +812,8 @@ extension AppManager
} }
} }
Task{ let operation = AppOperation.deactivate(installedApp)
await self.perform([.deactivate(installedApp)], presentingViewController: presentingViewController, group: group) self.perform([operation], presentingViewController: presentingViewController, group: group)
}
} }
} }
@@ -841,9 +837,8 @@ extension AppManager
} }
} }
Task{ let operation = AppOperation.backup(installedApp)
await self.perform([.backup(installedApp)], presentingViewController: presentingViewController, group: group) self.perform([operation], presentingViewController: presentingViewController, group: group)
}
} }
func restore(_ installedApp: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) func restore(_ installedApp: InstalledApp, presentingViewController: UIViewController?, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void)
@@ -868,9 +863,8 @@ extension AppManager
} }
} }
Task{ let operation = AppOperation.restore(installedApp)
await self.perform([.restore(installedApp)], presentingViewController: presentingViewController, group: group) self.perform([operation], presentingViewController: presentingViewController, group: group)
}
} }
func remove(_ installedApp: InstalledApp, completionHandler: @escaping (Result<Void, Error>) -> Void) func remove(_ installedApp: InstalledApp, completionHandler: @escaping (Result<Void, Error>) -> Void)
@@ -1097,7 +1091,7 @@ private extension AppManager
} }
@discardableResult @discardableResult
private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) async -> RefreshGroup private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) -> RefreshGroup
{ {
let operations = operations.filter { self.progress(for: $0) == nil || self.progress(for: $0)?.isCancelled == true } let operations = operations.filter { self.progress(for: $0) == nil || self.progress(for: $0)?.isCancelled == true }
@@ -1159,10 +1153,38 @@ private extension AppManager
case .activate(let app) where UserDefaults.standard.isLegacyDeactivationSupported: fallthrough case .activate(let app) where UserDefaults.standard.isLegacyDeactivationSupported: fallthrough
case .refresh(let app): case .refresh(let app):
let refreshProgress = self._refresh(app, operation: operation, group: group) { (result) in // Check if backup app is installed in place of real app.
self.finish(operation, result: result, group: group, progress: progress) // let altBackupUti = UTTypeCopyDeclaration(app.installedBackupAppUTI as CFString)?.takeRetainedValue() as NSDictionary?
}
progress?.addChild(refreshProgress, withPendingUnitCount: 80) // 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)
// }
case .activate(let app): case .activate(let app):
let activateProgress = self._activate(app, operation: operation, group: group) { (result) in let activateProgress = self._activate(app, operation: operation, group: group) { (result) in
self.finish(operation, result: result, group: group, progress: progress) self.finish(operation, result: result, group: group, progress: progress)
@@ -1232,10 +1254,21 @@ private extension AppManager
{ {
let progress = Progress.discreteProgress(totalUnitCount: 100) let progress = Progress.discreteProgress(totalUnitCount: 100)
let context = InstallAppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context) let context = context ?? InstallAppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context)
assert(context.authenticatedContext === group.context) assert(context.authenticatedContext === group.context)
context.beginInstallationHandler = { (installedApp) in 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) group.beginInstallationHandler?(installedApp)
} }
@@ -1256,6 +1289,20 @@ 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 */ /* Download */
let downloadedAppURL = context.temporaryDirectory.appendingPathComponent("Cached.app") let downloadedAppURL = context.temporaryDirectory.appendingPathComponent("Cached.app")
let downloadOperation = DownloadAppOperation(app: downloadingApp, destinationURL: downloadedAppURL, context: context) let downloadOperation = DownloadAppOperation(app: downloadingApp, destinationURL: downloadedAppURL, context: context)
@@ -1267,8 +1314,7 @@ private extension AppManager
if cacheApp if cacheApp
{ {
let updatedApp = AnyApp(from: app, bundleId: context.bundleIdentifier) try FileManager.default.copyItem(at: app.fileURL, to: InstalledApp.fileURL(for: app), shouldReplace: true)
try FileManager.default.copyItem(at: app.fileURL, to: InstalledApp.fileURL(for: updatedApp), shouldReplace: true)
} }
} }
catch catch
@@ -1278,9 +1324,15 @@ private extension AppManager
} }
progress.addChild(downloadOperation.progress, withPendingUnitCount: 25) progress.addChild(downloadOperation.progress, withPendingUnitCount: 25)
if let verifyPledgeOperation
{
downloadOperation.addDependency(verifyPledgeOperation)
}
/* Verify App */ /* Verify App */
let permissionsMode = UserDefaults.shared.permissionCheckingDisabled ? .none : permissionReviewMode let permissionsMode = UserDefaults.shared.permissionCheckingDisabled ? .none : permissionReviewMode
let verifyOperation = VerifyAppOperation(permissionsMode: permissionsMode, context: context, customBundleId: app.bundleIdentifier) let verifyOperation = VerifyAppOperation(permissionsMode: permissionsMode, context: context)
verifyOperation.resultHandler = { (result) in verifyOperation.resultHandler = { (result) in
do do
{ {
@@ -1433,7 +1485,7 @@ private extension AppManager
let patchAppURL = URL(string: patchAppLink) let patchAppURL = URL(string: patchAppLink)
else { throw OperationError.invalidApp } else { throw OperationError.invalidApp }
let patchApp = AnyApp(name: app.name, bundleIdentifier: context.bundleIdentifier, url: patchAppURL, storeApp: nil) let patchApp = AnyApp(name: app.name, bundleIdentifier: app.bundleIdentifier, url: patchAppURL, storeApp: nil)
DispatchQueue.main.async { DispatchQueue.main.async {
let storyboard = UIStoryboard(name: "PatchApp", bundle: nil) let storyboard = UIStoryboard(name: "PatchApp", bundle: nil)
@@ -1467,24 +1519,6 @@ private extension AppManager
patchAppOperation.addDependency(deactivateAppsOperation) 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 */ /* Resign */
let resignAppOperation = ResignAppOperation(context: context) let resignAppOperation = ResignAppOperation(context: context)
resignAppOperation.resultHandler = { (result) in resignAppOperation.resultHandler = { (result) in
@@ -1499,7 +1533,6 @@ private extension AppManager
} }
} }
resignAppOperation.addDependency(patchAppOperation) resignAppOperation.addDependency(patchAppOperation)
resignAppOperation.addDependency(modifyAppExBundleIdOperation)
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20) progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
@@ -1545,6 +1578,7 @@ private extension AppManager
// Operations picked for request // Operations picked for request
var operations = [ var operations = [
verifyPledgeOperation,
downloadOperation, downloadOperation,
verifyOperation, verifyOperation,
removeAppExtensionsOperation, removeAppExtensionsOperation,
@@ -1552,7 +1586,6 @@ private extension AppManager
patchAppOperation, patchAppOperation,
refreshAnisetteDataOperation, refreshAnisetteDataOperation,
fetchProvisioningProfilesOperation, fetchProvisioningProfilesOperation,
modifyAppExBundleIdOperation,
resignAppOperation, resignAppOperation,
sendAppOperation, sendAppOperation,
installOperation installOperation
@@ -1632,7 +1665,7 @@ private extension AppManager
let context = AppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context) let context = AppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context)
context.app = ALTApplication(fileURL: app.fileURL) context.app = ALTApplication(fileURL: app.fileURL)
context.useMainProfile = app.useMainProfile
// Since this doesn't involve modifying app bundle which will cause re-install, this is safe in refresh path // 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 //App-Extensions: Ensure DB data and disk state must match
let dbAppEx: Set<InstalledExtension> = Set(app.appExtensions) let dbAppEx: Set<InstalledExtension> = Set(app.appExtensions)
@@ -2092,6 +2125,27 @@ private extension AppManager
self.scheduleExpirationWarningLocalNotification(for: installedApp) 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 // Ask widgets to be refreshed
WidgetCenter.shared.reloadAllTimelines() WidgetCenter.shared.reloadAllTimelines()
@@ -2176,7 +2230,7 @@ private extension AppManager
switch operation switch operation
{ {
case _ where requiresSerialQueue: fallthrough case _ where requiresSerialQueue: fallthrough
case is InstallAppOperation, is RefreshAppOperation, is BackupAppOperation: case is InstallAppOperation, is RefreshAppOperation, is BackupAppOperation, is VerifyAppPledgeOperation:
if let installAltStoreOperation = operation as? InstallAppOperation, installAltStoreOperation.context.bundleIdentifier == StoreApp.altstoreAppID 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. // Add dependencies on previous serial operations in `context` to ensure re-installing AltStore goes last.
@@ -2228,126 +2282,3 @@ 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)
}
}
}

View File

@@ -166,11 +166,9 @@ class MyAppsViewController: UICollectionViewController, PeekPopPreviewing
@IBAction func unwindToMyAppsViewController(_ segue: UIStoryboardSegue) @IBAction func unwindToMyAppsViewController(_ segue: UIStoryboardSegue)
{ {
} }
var minimuxerStatus: Bool { var minimuxerStatus: Bool {
// added isMinimuxerStatusCheckEnabled to forcefully ignore minimuxer status if status check is disabled in settings guard minimuxer.ready() else {
guard !UserDefaults.standard.isMinimuxerStatusCheckEnabled || minimuxer.ready() else { ToastView(error: (OperationError.noWiFi as NSError).withLocalizedTitle("No WiFi or VPN!")).show(in: self)
ToastView(error: (OperationError.noWiFi as NSError).withLocalizedTitle("No Wi-Fi or VPN!")).show(in: self)
return false return false
} }
return true return true
@@ -544,6 +542,11 @@ private extension MyAppsViewController
{ {
print("[ALTLog] Failed to fetch updates:", error) print("[ALTLog] Failed to fetch updates:", error)
} }
if let patreonAccount = DatabaseManager.shared.patreonAccount(), patreonAccount.isAltStorePatron, PatreonAPI.shared.isAuthenticated
{
self.dataSource.predicate = nil
}
} }
} }
@@ -1482,6 +1485,15 @@ private extension MyAppsViewController
guard minimuxerStatus else { return } 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 AppManager.shared.enableJIT(for: installedApp) { result in
DispatchQueue.main.async { DispatchQueue.main.async {
switch result { switch result {

View File

@@ -85,86 +85,70 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
return return
} }
Task { // Sign In
// try to use cached session self.signIn() { (result) in
if guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
let certificate = Keychain.shared.certificate,
let session = Keychain.shared.session, switch result
let team = Keychain.shared.team
{ {
if session.anisetteData.date.timeIntervalSinceNow < -40.0 { case .failure(let error): self.finish(.failure(error))
let anisetteData = try await withUnsafeThrowingContinuation { (c: UnsafeContinuation<ALTAnisetteData, any Error>) in case .success((let account, let session)):
let fetchAnisetteDataOperation = FetchAnisetteDataOperation(context: self.context) self.context.session = session
fetchAnisetteDataOperation.resultHandler = { (result) in self.progress.completedUnitCount += 1
c.resume(with: result)
// 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)
}
}
}
}
}
}
} }
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))
} }
} }
} }
@@ -375,29 +359,6 @@ 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 if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword
{ {
Logger.sideload.notice("Authenticating Apple ID...") Logger.sideload.notice("Authenticating Apple ID...")
@@ -423,25 +384,6 @@ 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) func authenticate(appleID: String, password: String, completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Swift.Error>) -> Void)
{ {
self.appleIDEmailAddress = appleID self.appleIDEmailAddress = appleID
@@ -502,8 +444,6 @@ private extension AuthenticationOperation
verificationHandler: verificationHandler) { (account, session, error) in verificationHandler: verificationHandler) { (account, session, error) in
if let account = account, let session = session if let account = account, let session = session
{ {
Keychain.shared.appleIDAdsid = session.dsid
Keychain.shared.appleIDXcodeToken = session.authToken
completionHandler(.success((account, session))) completionHandler(.success((account, session)))
} }
else else
@@ -807,30 +747,3 @@ extension AuthenticationOperation
self.submitCodeAction?.isEnabled = (textField.text ?? "").count == 6 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))
}
}
}
}

View File

@@ -99,10 +99,7 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
self.finish(.failure(RefreshError(.noInstalledApps))) self.finish(.failure(RefreshError(.noInstalledApps)))
return return
} }
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
if UserDefaults.standard.enableEMPforWireguard {
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
}
target_minimuxer_address() target_minimuxer_address()
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
do { do {

View File

@@ -38,7 +38,7 @@ final class DownloadAppOperation: ResultOperation<ALTApplication>
self.context = context self.context = context
self.appName = app.name self.appName = app.name
self.bundleIdentifier = context.bundleIdentifier self.bundleIdentifier = app.bundleIdentifier
self.sourceURL = app.url self.sourceURL = app.url
self.destinationURL = destinationURL self.destinationURL = destinationURL
@@ -77,7 +77,7 @@ final class DownloadAppOperation: ResultOperation<ALTApplication>
guard let latestVersion = storeApp.latestAvailableVersion else { guard let latestVersion = storeApp.latestAvailableVersion else {
let failureReason = String(format: NSLocalizedString("The latest version of %@ could not be determined.", comment: ""), self.appName) let failureReason = String(format: NSLocalizedString("The latest version of %@ could not be determined.", comment: ""), self.appName)
throw OperationError.unknown(failureReason: failureReason) throw OperationError.unknown(failureReason: failureReason)
} }
// Attempt to download latest _available_ version, and fall back to older versions if necessary. // Attempt to download latest _available_ version, and fall back to older versions if necessary.
appVersion = latestVersion appVersion = latestVersion
@@ -224,6 +224,12 @@ private extension DownloadAppOperation
fileURL = sourceURL fileURL = sourceURL
self.progress.completedUnitCount += 3 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 else
{ {
// Regular app // Regular app
@@ -317,6 +323,107 @@ private extension DownloadAppOperation
self.printWithTid("download started: \(downloadURL)") 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
}
} }
} }

View File

@@ -201,7 +201,7 @@ struct OperationError: ALTLocalizedError {
case .unknownResult: return NSLocalizedString("The operation returned an unknown result.", comment: "") case .unknownResult: return NSLocalizedString("The operation returned an unknown result.", comment: "")
case .timedOut: return NSLocalizedString("The operation timed out.", comment: "") case .timedOut: return NSLocalizedString("The operation timed out.", comment: "")
case .notAuthenticated: return NSLocalizedString("You are not signed in.", comment: "") case .notAuthenticated: return NSLocalizedString("You are not signed in.", comment: "")
case .unknownUDID: return NSLocalizedString("SideStore could not determine this device's UDID. Please replace your pairing using iloader.", comment: "") case .unknownUDID: return NSLocalizedString("SideStore could not determine this device's UDID.", comment: "")
case .invalidApp: return NSLocalizedString("The app is in an invalid format.", 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 .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs within a 7 day period.", comment: "")
case .noSources: return NSLocalizedString("There are no SideStore sources.", comment: "") case .noSources: return NSLocalizedString("There are no SideStore sources.", comment: "")
@@ -220,16 +220,16 @@ struct OperationError: ALTLocalizedError {
case .openAppFailed: case .openAppFailed:
let appName = self.appName ?? NSLocalizedString("The app", comment: "") let appName = self.appName ?? NSLocalizedString("The app", comment: "")
return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), appName) return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), appName)
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 .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.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 .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 of and your Firewall has been set correctly on your server.", 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 iDevice by running 'SideJITServer -y', or try refreshing SideJITServer from Settings.", 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 same Wi-Fi as SideJITServer", 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 .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 .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 while provisioning: %@ %@. Please try again. If the issue persists, report it on GitHub Issues!", 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 while getting anisette data from a V3 server: %@. 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 the cache: %@", comment: "") case .cacheClearError: return NSLocalizedString("An error occurred while clearing cache: %@", comment: "")
case .SideJITIssue: return NSLocalizedString("An error occurred while using SideJIT: %@", comment: "") case .SideJITIssue: return NSLocalizedString("An error occurred while using SideJIT: %@", comment: "")
case .refreshAppFailed: case .refreshAppFailed:
@@ -260,7 +260,7 @@ struct OperationError: ALTLocalizedError {
var recoverySuggestion: String? { var recoverySuggestion: String? {
switch self.code switch self.code
{ {
case .noWiFi: return NSLocalizedString("Make sure LocalDevVPN is connected and that you are connected to any Wi-Fi network!", comment: "") case .noWiFi: return NSLocalizedString("Make sure the VPN is toggled on and you are connected to any WiFi 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 .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: case .maximumAppIDLimitReached:
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "") let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
@@ -308,9 +308,9 @@ extension MinimuxerError: LocalizedError {
case .NoDevice: case .NoDevice:
return NSLocalizedString("Cannot fetch the device from the muxer", comment: "") return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
case .NoConnection: case .NoConnection:
return NSLocalizedString("Unable to connect to the device, make sure LocalDevVPN is enabled and you're connected to Wi-Fi. This could mean an invalid pairing.", comment: "") return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi. This could mean an invalid pairing.", comment: "")
case .PairingFile: case .PairingFile:
return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use iloader to replace it.", comment: "") return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use jitterbugpair to generate it", comment: "")
case .CreateDebug: case .CreateDebug:
return self.createService(name: "debug") return self.createService(name: "debug")
@@ -338,7 +338,7 @@ extension MinimuxerError: LocalizedError {
case .CreateAfc: case .CreateAfc:
return self.createService(name: "AFC") return self.createService(name: "AFC")
case .RwAfc: case .RwAfc:
return NSLocalizedString("AFC was unable to manage files on the device. Ensure Wi-Fi and LocalDevVPN are connected. If they both are, replace your pairing using iloader.", comment: "") return NSLocalizedString("AFC was unable to manage files on the device. This usually means an invalid pairing.", comment: "")
case .InstallApp(let message): case .InstallApp(let message):
return NSLocalizedString("Unable to install the app: \(message.toString())", comment: "") return NSLocalizedString("Unable to install the app: \(message.toString())", comment: "")
case .UninstallApp: case .UninstallApp:
@@ -350,38 +350,6 @@ extension MinimuxerError: LocalizedError {
return NSLocalizedString("Unable to manage profiles on the device", comment: "") return NSLocalizedString("Unable to manage profiles on the device", comment: "")
case .ProfileRemove: case .ProfileRemove:
return NSLocalizedString("Unable to manage profiles on the device", comment: "") 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: "")
} }
} }

View File

@@ -198,7 +198,7 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSoc
let formatter = DateFormatter() let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX") formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.calendar = Calendar(identifier: .gregorian) formatter.calendar = Calendar(identifier: .gregorian)
formatter.timeZone = TimeZone.init(secondsFromGMT: 0) formatter.timeZone = TimeZone.current
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
let dateString = formatter.string(from: Date()) let dateString = formatter.string(from: Date())
formattedJSON["date"] = dateString formattedJSON["date"] = dateString

View File

@@ -54,7 +54,6 @@ class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioni
Logger.sideload.notice("Fetching provisioning profiles for app \(self.context.bundleIdentifier, privacy: .public)...") Logger.sideload.notice("Fetching provisioning profiles for app \(self.context.bundleIdentifier, privacy: .public)...")
self.progress.totalUnitCount = Int64(1 + app.appExtensions.count) 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 self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in
do do
@@ -63,27 +62,25 @@ class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioni
let profile = try result.get() let profile = try result.get()
var profiles = [effectiveBundleId: profile] var profiles = [app.bundleIdentifier: profile]
var error: Error? var error: Error?
let dispatchGroup = DispatchGroup() let dispatchGroup = DispatchGroup()
if !self.context.useMainProfile { for appExtension in app.appExtensions
for appExtension in app.appExtensions {
{ dispatchGroup.enter()
dispatchGroup.enter()
self.prepareProvisioningProfile(for: appExtension, parentApp: app, team: team, session: session) { (result) in self.prepareProvisioningProfile(for: appExtension, parentApp: app, team: team, session: session) { (result) in
switch result switch result
{ {
case .failure(let e): error = e case .failure(let e): error = e
case .success(let profile): profiles[appExtension.bundleIdentifier] = profile case .success(let profile): profiles[appExtension.bundleIdentifier] = profile
}
dispatchGroup.leave()
self.progress.completedUnitCount += 1
} }
dispatchGroup.leave()
self.progress.completedUnitCount += 1
} }
} }
@@ -221,30 +218,19 @@ extension FetchProvisioningProfilesOperation
// Or, if the app _is_ installed but with a different team, we need to create a new // 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. // bundle identifier anyway to prevent collisions with the previous team.
let parentBundleID = parentApp?.bundleIdentifier ?? app.bundleIdentifier let parentBundleID = parentApp?.bundleIdentifier ?? app.bundleIdentifier
let effectiveParentBundleID = self.context.bundleIdentifier
let updatedParentBundleID: String let updatedParentBundleID: String
if app.isAltStoreApp if app.isAltStoreApp
{ {
// Use legacy bundle ID format for AltStore (and its extensions). // Use legacy bundle ID format for AltStore (and its extensions).
updatedParentBundleID = effectiveParentBundleID + "." + team.identifier // Append just team identifier to make it harder to track. updatedParentBundleID = parentBundleID + "." + team.identifier // Append just team identifier to make it harder to track.
} }
else else
{ {
updatedParentBundleID = effectiveParentBundleID + "." + team.identifier // Append just team identifier to make it harder to track. updatedParentBundleID = parentBundleID + "." + team.identifier // Append just team identifier to make it harder to track.
} }
if let parentApp = parentApp, bundleID = app.bundleIdentifier.replacingOccurrences(of: parentBundleID, with: updatedParentBundleID)
app.bundleIdentifier.hasPrefix(parentBundleID + ".")
{
let suffix = String(app.bundleIdentifier.dropFirst(parentBundleID.count))
bundleID = updatedParentBundleID + suffix
}
else
{
bundleID = updatedParentBundleID
}
} }
let preferredName: String let preferredName: String
@@ -374,6 +360,13 @@ extension FetchProvisioningProfilesOperation
} }
} }
class FetchProvisioningProfilesRefreshOperation: FetchProvisioningProfilesOperation, @unchecked Sendable {
override init(context: AppOperationContext)
{
super.init(context: context)
}
}
class FetchProvisioningProfilesInstallOperation: FetchProvisioningProfilesOperation, @unchecked Sendable{ class FetchProvisioningProfilesInstallOperation: FetchProvisioningProfilesOperation, @unchecked Sendable{
override init(context: AppOperationContext) override init(context: AppOperationContext)
{ {
@@ -620,14 +613,3 @@ class FetchProvisioningProfilesInstallOperation: FetchProvisioningProfilesOperat
} }
} }
} }
// <TEST> : users were reporting that refresh (though seemed like it refreshed the app becomes no longer available)
// possibly, this is caused since refesh was not updating appFeatures and AppGroups in the new profile? not sure.
// for now we are reverting by keeping same operation that happens during fetch in install path to see if it fixes issue #893
// class FetchProvisioningProfilesRefreshOperation: FetchProvisioningProfilesOperation, @unchecked Sendable {
class FetchProvisioningProfilesRefreshOperation: FetchProvisioningProfilesInstallOperation, @unchecked Sendable {
override init(context: AppOperationContext)
{
super.init(context: context)
}
}

View File

@@ -11,7 +11,6 @@ import CoreData
import AltStoreCore import AltStoreCore
import Roxas import Roxas
import SemanticVersion
@objc(FetchSourceOperation) @objc(FetchSourceOperation)
final class FetchSourceOperation: ResultOperation<Source> final class FetchSourceOperation: ResultOperation<Source>
@@ -177,6 +176,7 @@ final class FetchSourceOperation: ResultOperation<Source>
} }
try self.verify(source, response: response) try self.verify(source, response: response)
try self.verifyPledges(for: source, in: childContext)
try childContext.save() try childContext.save()
@@ -246,20 +246,66 @@ private extension FetchSourceOperation
#endif #endif
} }
let incomingSourceID = source.identifier if let previousSourceID = self.$source.identifier
if let previousSourceID = self.$source.identifier,
incomingSourceID != previousSourceID
{ {
// if let version = BuildInfo().marketing_version, guard source.identifier == previousSourceID else { throw SourceError.changedID(source.identifier, previousID: previousSourceID, source: source) }
// SemanticVersion(version)! <= SemanticVersion("0.6.1")! }
// { }
// // delete the source, so that incoming will be saved.
// self.source?.managedObjectContext?.delete(self.source!) func verifyPledges(for source: Source, in context: NSManagedObjectContext) throws
// } {
// else guard let patreonURL = source.patreonURL, let patreonAccount = DatabaseManager.shared.patreonAccount(in: context) else { return }
// {
throw SourceError.changedID(source.identifier, previousID: self.$source.identifier ?? "nil", source: source) 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
}
}
} }
} }

View File

@@ -72,8 +72,6 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
} }
installedApp.update(resignedApp: resignedApp, certificateSerialNumber: certificate.serialNumber, storeBuildVersion: storeBuildVersion) installedApp.update(resignedApp: resignedApp, certificateSerialNumber: certificate.serialNumber, storeBuildVersion: storeBuildVersion)
installedApp.useMainProfile = self.context.useMainProfile
installedApp.needsResign = false installedApp.needsResign = false
if let team = DatabaseManager.shared.activeTeam(in: backgroundContext) if let team = DatabaseManager.shared.activeTeam(in: backgroundContext)
@@ -98,22 +96,22 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
let resignedParentBundleID = resignedApp.bundleIdentifier let resignedParentBundleID = resignedApp.bundleIdentifier
let resignedBundleID = appExtension.bundleIdentifier let resignedBundleID = appExtension.bundleIdentifier
let appExBundleID = resignedBundleID.replacingOccurrences(of: resignedParentBundleID, with: parentBundleID) let originalBundleID = resignedBundleID.replacingOccurrences(of: resignedParentBundleID, with: parentBundleID)
print("`parentBundleID`: \(parentBundleID)") print("`parentBundleID`: \(parentBundleID)")
print("`resignedParentBundleID`: \(resignedParentBundleID)") print("`resignedParentBundleID`: \(resignedParentBundleID)")
print("`appExBundleID`: \(appExBundleID)") print("`resignedBundleID`: \(resignedBundleID)")
print("`resignedAppExBundleID`: \(resignedBundleID)") print("`originalBundleID`: \(originalBundleID)")
let installedExtension: InstalledExtension let installedExtension: InstalledExtension
if let appExtension = installedApp.appExtensions.first(where: { $0.bundleIdentifier == appExBundleID }) if let appExtension = installedApp.appExtensions.first(where: { $0.bundleIdentifier == originalBundleID })
{ {
installedExtension = appExtension installedExtension = appExtension
} }
else else
{ {
installedExtension = InstalledExtension(resignedAppExtension: appExtension, originalBundleIdentifier: appExBundleID, context: backgroundContext) installedExtension = InstalledExtension(resignedAppExtension: appExtension, originalBundleIdentifier: originalBundleID, context: backgroundContext)
} }
installedExtension.update(resignedAppExtension: appExtension) installedExtension.update(resignedAppExtension: appExtension)

View File

@@ -66,8 +66,6 @@ class AppOperationContext
var app: ALTApplication? var app: ALTApplication?
var provisioningProfiles: [String: ALTProvisioningProfile]? var provisioningProfiles: [String: ALTProvisioningProfile]?
var appexBundleIds: [String: String]?
var useMainProfile = false
var isFinished = false var isFinished = false

View File

@@ -136,11 +136,7 @@ final class RemoveAppExtensionsOperation: ResultOperation<Void>
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
self.finish(.failure(OperationError.cancelled)) self.finish(.failure(OperationError.cancelled))
})) }))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions (Use Main Profile)", comment: ""), style: .default) { (action) in alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", 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(())) self.finish(.success(()))
}) })
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in

View File

@@ -55,8 +55,7 @@ final class ResignAppOperation: ResultOperation<ALTApplication>
let prepareAppProgress = Progress.discreteProgress(totalUnitCount: 2) let prepareAppProgress = Progress.discreteProgress(totalUnitCount: 2)
self.progress.addChild(prepareAppProgress, withPendingUnitCount: 3) self.progress.addChild(prepareAppProgress, withPendingUnitCount: 3)
let effectiveBundleId = self.context.bundleIdentifier let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles) { (result) in
let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles, appexBundleIds: context.appexBundleIds ?? [:]) { (result) in
guard let appBundleURL = self.process(result) else { return } guard let appBundleURL = self.process(result) else { return }
// Resign app bundle // Resign app bundle
@@ -66,13 +65,7 @@ final class ResignAppOperation: ResultOperation<ALTApplication>
// Finish // Finish
do do
{ {
let updatedApp = AnyApp( let destinationURL = InstalledApp.refreshedIPAURL(for: app)
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) try FileManager.default.copyItem(at: resignedURL, to: destinationURL, shouldReplace: true)
print("Successfully resigned app to \(destinationURL.absoluteString)") print("Successfully resigned app to \(destinationURL.absoluteString)")
@@ -114,26 +107,22 @@ final class ResignAppOperation: ResultOperation<ALTApplication>
private extension ResignAppOperation private extension ResignAppOperation
{ {
func prepareAppBundle(for app: ALTApplication, profiles: [String: ALTProvisioningProfile], appexBundleIds: [String: String], completionHandler: @escaping (Result<URL, Error>) -> Void) -> Progress func prepareAppBundle(for app: ALTApplication, profiles: [String: ALTProvisioningProfile], completionHandler: @escaping (Result<URL, Error>) -> Void) -> Progress
{ {
let progress = Progress.discreteProgress(totalUnitCount: 1) let progress = Progress.discreteProgress(totalUnitCount: 1)
let bundleIdentifier = context.bundleIdentifier let bundleIdentifier = app.bundleIdentifier
let openURL = InstalledApp.openAppURL(for: app) let openURL = InstalledApp.openAppURL(for: app)
let fileURL = app.fileURL let fileURL = app.fileURL
func prepare(_ bundle: Bundle, bundleID identifier: String?, additionalInfoDictionaryValues: [String: Any] = [:]) throws func prepare(_ bundle: Bundle, additionalInfoDictionaryValues: [String: Any] = [:]) throws
{ {
guard let identifier else { throw ALTError(.missingAppBundle) } guard let identifier = bundle.bundleIdentifier else { throw ALTError(.missingAppBundle) }
guard let profile = context.useMainProfile ? profiles.values.first : profiles[identifier] else { throw ALTError(.missingProvisioningProfile) } guard let profile = profiles[identifier] else { throw ALTError(.missingProvisioningProfile) }
guard var infoDictionary = bundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) } guard var infoDictionary = bundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) }
if let forcedBundleIdentifier = appexBundleIds[identifier] { infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
infoDictionary[kCFBundleIdentifierKey as String] = forcedBundleIdentifier
} else {
infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
}
infoDictionary[Bundle.Info.altBundleID] = identifier infoDictionary[Bundle.Info.altBundleID] = identifier
infoDictionary[Bundle.Info.devicePairingString] = "<insert pairing file here>" infoDictionary[Bundle.Info.devicePairingString] = "<insert pairing file here>"
infoDictionary.removeValue(forKey: "DTXcode") infoDictionary.removeValue(forKey: "DTXcode")
@@ -204,7 +193,7 @@ private extension ResignAppOperation
if app.isAltStoreApp if app.isAltStoreApp
{ {
guard let udid = fetch_udid()?.toString() as? String else { throw OperationError.unknownUDID } guard let udid = fetch_udid()?.toString() as? String else { throw OperationError.unknownUDID }
guard Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) is String else { throw OperationError.unknownUDID } guard let pairingFileString = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) as? String else { throw OperationError.unknownUDID }
additionalValues[Bundle.Info.devicePairingString] = "<insert pairing file here>" additionalValues[Bundle.Info.devicePairingString] = "<insert pairing file here>"
additionalValues[Bundle.Info.deviceID] = udid additionalValues[Bundle.Info.deviceID] = udid
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.preferredServerID additionalValues[Bundle.Info.serverID] = UserDefaults.standard.preferredServerID
@@ -248,7 +237,7 @@ private extension ResignAppOperation
} }
// Prepare app // Prepare app
try prepare(appBundle, bundleID: bundleIdentifier, additionalInfoDictionaryValues: additionalValues) try prepare(appBundle, additionalInfoDictionaryValues: additionalValues)
try self.removeMissingAppExtensionReferences(from: appBundle) try self.removeMissingAppExtensionReferences(from: appBundle)
if let directory = appBundle.builtInPlugInsURL, let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants]) if let directory = appBundle.builtInPlugInsURL, let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants])
@@ -265,8 +254,7 @@ private extension ResignAppOperation
#endif #endif
guard let appExtension = Bundle(url: fileURL) else { throw ALTError(.missingAppBundle) } guard let appExtension = Bundle(url: fileURL) else { throw ALTError(.missingAppBundle) }
let updatedAppExBundleId = appExtension.bundleIdentifier?.replacingOccurrences(of: app.bundleIdentifier, with: bundleIdentifier) try prepare(appExtension)
try prepare(appExtension, bundleID: updatedAppExBundleId)
} }
} }

View File

@@ -38,13 +38,11 @@ final class VerifyAppOperation: ResultOperation<Void>
{ {
let permissionsMode: PermissionReviewMode let permissionsMode: PermissionReviewMode
let context: InstallAppOperationContext let context: InstallAppOperationContext
var customBundleId: String?
init(permissionsMode: PermissionReviewMode, context: InstallAppOperationContext, customBundleId: String? = nil) init(permissionsMode: PermissionReviewMode, context: InstallAppOperationContext)
{ {
self.permissionsMode = permissionsMode self.permissionsMode = permissionsMode
self.context = context self.context = context
self.customBundleId = customBundleId
super.init() super.init()
} }
@@ -67,8 +65,7 @@ final class VerifyAppOperation: ResultOperation<Void>
} }
if !["ny.litritt.ignited", "com.litritt.ignited"].contains(where: { $0 == app.bundleIdentifier }) { if !["ny.litritt.ignited", "com.litritt.ignited"].contains(where: { $0 == app.bundleIdentifier }) {
let bundleId = customBundleId ?? app.bundleIdentifier guard app.bundleIdentifier == self.context.bundleIdentifier else {
guard bundleId == self.context.bundleIdentifier else {
throw VerificationError.mismatchedBundleIdentifiers(sourceBundleID: self.context.bundleIdentifier, app: app) throw VerificationError.mismatchedBundleIdentifiers(sourceBundleID: self.context.bundleIdentifier, app: app)
} }
} }

View File

@@ -0,0 +1,281 @@
//
// 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()
}
}

View File

@@ -8,8 +8,6 @@
<key>name</key> <key>name</key>
<string>Original</string> <string>Original</string>
<key>imageName</key> <key>imageName</key>
<string>App</string>
<key>iconName</key>
<string>AppIcon</string> <string>AppIcon</string>
</dict> </dict>
</array> </array>
@@ -20,88 +18,66 @@
<string>Blue</string> <string>Blue</string>
<key>imageName</key> <key>imageName</key>
<string>Blue</string> <string>Blue</string>
<key>iconName</key>
<string>BlueIcon</string>
</dict> </dict>
<dict> <dict>
<key>name</key> <key>name</key>
<string>Dark</string> <string>Dark</string>
<key>imageName</key> <key>imageName</key>
<string>Dark</string> <string>Dark</string>
<key>iconName</key>
<string>DarkIcon</string>
</dict> </dict>
<dict> <dict>
<key>name</key> <key>name</key>
<string>Honeydew</string> <string>Honeydew</string>
<key>imageName</key> <key>imageName</key>
<string>Honeydew</string> <string>Honeydew</string>
<key>iconName</key>
<string>HoneydewIcon</string>
</dict> </dict>
<dict> <dict>
<key>name</key> <key>name</key>
<string>Pride</string> <string>Pride</string>
<key>imageName</key> <key>imageName</key>
<string>Pride</string> <string>Pride</string>
<key>iconName</key>
<string>PrideIcon</string>
</dict> </dict>
<dict> <dict>
<key>name</key> <key>name</key>
<string>Sandy</string> <string>Sandy</string>
<key>imageName</key> <key>imageName</key>
<string>Sandy</string> <string>Sandy</string>
<key>iconName</key>
<string>SandyIcon</string>
</dict> </dict>
<dict> <dict>
<key>name</key> <key>name</key>
<string>Sky</string> <string>Sky</string>
<key>imageName</key> <key>imageName</key>
<string>Sky</string> <string>Sky</string>
<key>iconName</key>
<string>SkyIcon</string>
</dict> </dict>
<dict> <dict>
<key>name</key> <key>name</key>
<string>Snow</string> <string>Snow</string>
<key>imageName</key> <key>imageName</key>
<string>Snow</string> <string>Snow</string>
<key>iconName</key>
<string>SnowIcon</string>
</dict> </dict>
<dict> <dict>
<key>name</key> <key>name</key>
<string>Starburst</string> <string>Starburst</string>
<key>imageName</key> <key>imageName</key>
<string>Starburst</string> <string>Starburst</string>
<key>iconName</key>
<string>StarburstIcon</string>
</dict> </dict>
<dict> <dict>
<key>name</key> <key>name</key>
<string>Storm</string> <string>Storm</string>
<key>imageName</key> <key>imageName</key>
<string>Storm</string> <string>Storm</string>
<key>iconName</key>
<string>StormIcon</string>
</dict> </dict>
<dict> <dict>
<key>name</key> <key>name</key>
<string>Vista</string> <string>Vista</string>
<key>imageName</key> <key>imageName</key>
<string>Vista</string> <string>Vista</string>
<key>iconName</key>
<string>VistaIcon</string>
</dict> </dict>
<dict> <dict>
<key>name</key> <key>name</key>
<string>Winter</string> <string>Winter</string>
<key>imageName</key> <key>imageName</key>
<string>Winter</string> <string>Winter</string>
<key>iconName</key>
<string>WinterIcon</string>
</dict> </dict>
</array> </array>
</dict> </dict>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "App.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

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

View File

Before

Width:  |  Height:  |  Size: 313 KiB

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "Blue.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "Dark.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "Honeydew.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 719 KiB

After

Width:  |  Height:  |  Size: 719 KiB

View File

@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "Pride.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

View File

Before

Width:  |  Height:  |  Size: 352 KiB

After

Width:  |  Height:  |  Size: 352 KiB

View File

@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "Sandy.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 135 KiB

View File

@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "Sky.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 453 KiB

After

Width:  |  Height:  |  Size: 453 KiB

View File

@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "Snow.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "Starburst.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 464 KiB

After

Width:  |  Height:  |  Size: 464 KiB

View File

@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "Storm.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "Vista.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

View File

@@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "Winter.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -40,9 +40,9 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate
guard DatabaseManager.shared.isStarted else { return } guard DatabaseManager.shared.isStarted else { return }
AppManager.shared.update() AppManager.shared.update()
if UserDefaults.standard.enableEMPforWireguard { start_em_proxy(bind_addr: Consts.Proxy.serverURL)
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
} PatreonAPI.shared.refreshPatreonAccount()
} }
func sceneDidEnterBackground(_ scene: UIScene) func sceneDidEnterBackground(_ scene: UIScene)
@@ -56,9 +56,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate
// Make sure to update AppDelegate.applicationDidEnterBackground() as well. // Make sure to update AppDelegate.applicationDidEnterBackground() as well.
// TODO: @mahee96: find if we need to stop em_proxy as in altstore? // TODO: @mahee96: find if we need to stop em_proxy as in altstore?
if UserDefaults.standard.enableEMPforWireguard { // stop_em_proxy()
stop_em_proxy()
}
guard let oneMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date()) else { return } guard let oneMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date()) else { return }
@@ -144,61 +142,8 @@ private extension SceneDelegate
NotificationCenter.default.post(name: AppDelegate.addSourceDeepLinkNotification, object: nil, userInfo: [AppDelegate.addSourceDeepLinkURLKey: sourceURL]) 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 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)
}
}

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<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"> <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">
<device id="retina6_1" orientation="portrait" appearance="light"/> <device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24504"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Named colors" minToolsVersion="9.0"/> <capability name="Named colors" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@@ -23,7 +23,7 @@
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="f7H-EV-7Sx"> <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"/> <rect key="frame" x="0.0" y="0.0" width="343" height="55"/>
<subviews> <subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="SideStore" translatesAutoresizingMaskIntoConstraints="NO" id="pn6-Ic-MJm" userLabel="Icon View"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="SideStore" translatesAutoresizingMaskIntoConstraints="NO" id="pn6-Ic-MJm">
<rect key="frame" x="0.0" y="0.0" width="55" height="55"/> <rect key="frame" x="0.0" y="0.0" width="55" height="55"/>
<constraints> <constraints>
<constraint firstAttribute="width" constant="55" id="7LH-UB-M1b"/> <constraint firstAttribute="width" constant="55" id="7LH-UB-M1b"/>
@@ -31,19 +31,36 @@
</constraints> </constraints>
</imageView> </imageView>
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="si2-MA-3RH"> <stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="si2-MA-3RH">
<rect key="frame" x="65" y="0.0" width="278" height="55"/> <rect key="frame" x="65" y="0.0" width="213" height="55"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="hkS-oz-wiT"> <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="278" height="55"/> <rect key="frame" x="0.0" y="0.0" width="100.5" height="55"/>
<subviews> <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" userLabel="Team name"> <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="278" height="21.5"/> <rect key="frame" x="0.0" y="0.0" width="100.5" height="21.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="18"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="18"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </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=" "> <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="278" height="31.5"/> <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"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" white="1" alpha="0.65000000000000002" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="0.65000000000000002" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -52,6 +69,14 @@
</stackView> </stackView>
</subviews> </subviews>
</stackView> </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> </subviews>
<constraints> <constraints>
<constraint firstAttribute="height" constant="55" id="Uiy-9X-WSO"/> <constraint firstAttribute="height" constant="55" id="Uiy-9X-WSO"/>
@@ -124,6 +149,7 @@ 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="instagramButton" destination="VdY-7Q-amF" id="5kj-9x-k4F"/>
<outlet property="rileyImageView" destination="pn6-Ic-MJm" id="60i-Q0-ojz"/> <outlet property="rileyImageView" destination="pn6-Ic-MJm" id="60i-Q0-ojz"/>
<outlet property="rileyLabel" destination="DTd-Yu-HXr" id="O0y-JB-gWp"/> <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="supportButton" destination="yEi-L6-kQ8" id="Dzo-vd-SnD"/>
<outlet property="textView" destination="FeG-e5-LJl" id="K0M-lF-I6u"/> <outlet property="textView" destination="FeG-e5-LJl" id="K0M-lF-I6u"/>
<outlet property="twitterButton" destination="hov-Ce-LaM" id="gib-Lt-qtY"/> <outlet property="twitterButton" destination="hov-Ce-LaM" id="gib-Lt-qtY"/>
@@ -132,7 +158,8 @@ Following us on social media allows us to give quick updates and spread the word
</collectionReusableView> </collectionReusableView>
</objects> </objects>
<resources> <resources>
<image name="SideStore" width="512" height="512"/> <image name="Shane" width="128" height="128"/>
<image name="SideStore" width="1024" height="1024"/>
<namedColor name="SettingsHighlighted"> <namedColor name="SettingsHighlighted">
<color red="0.38823529411764707" green="0.011764705882352941" blue="0.58823529411764708" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color red="0.38823529411764707" green="0.011764705882352941" blue="0.58823529411764708" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor> </namedColor>

View File

@@ -20,17 +20,15 @@ extension UIApplication
private final class AltIcon: Decodable private final class AltIcon: Decodable
{ {
static let defaultName: String = "Original" static let defaultIconName: String = "AppIcon"
var name: String var name: String
var imageName: String var imageName: String
var iconName: String
private enum CodingKeys: String, CodingKey private enum CodingKeys: String, CodingKey
{ {
case name case name
case imageName case imageName
case iconName
} }
required init(from decoder: Decoder) throws required init(from decoder: Decoder) throws
@@ -38,7 +36,6 @@ private final class AltIcon: Decodable
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name) self.name = try container.decode(String.self, forKey: .name)
self.imageName = try container.decode(String.self, forKey: .imageName) self.imageName = try container.decode(String.self, forKey: .imageName)
self.iconName = try container.decode(String.self, forKey: .iconName)
} }
} }
@@ -149,14 +146,20 @@ private extension AltAppIconsViewController
config.textProperties.font = font config.textProperties.font = font
config.textProperties.color = .label config.textProperties.color = .label
let image = UIImage(named: icon.imageName) // 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)
config.image = image config.image = image
config.imageProperties.maximumSize = CGSize(width: imageWidth, height: imageWidth) config.imageProperties.maximumSize = CGSize(width: imageWidth, height: imageWidth)
config.imageProperties.cornerRadius = imageWidth / 5.0 // Copied from AppIconImageView config.imageProperties.cornerRadius = imageWidth / 5.0 // Copied from AppIconImageView
cell.contentConfiguration = config cell.contentConfiguration = config
if UIApplication.shared.alternateIconName == icon.iconName || (UIApplication.shared.alternateIconName == nil && icon.name == AltIcon.defaultName) if UIApplication.shared.alternateIconName == icon.imageName || (UIApplication.shared.alternateIconName == nil && icon.imageName == AltIcon.defaultIconName)
{ {
cell.accessories = [.checkmark(options: .init(tintColor: .white))] cell.accessories = [.checkmark(options: .init(tintColor: .white))]
} }
@@ -196,14 +199,14 @@ extension AltAppIconsViewController
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{ {
let icon = self.dataSource.item(at: indexPath) let icon = self.dataSource.item(at: indexPath)
guard UIApplication.shared.alternateIconName != icon.iconName else { return } guard UIApplication.shared.alternateIconName != icon.imageName else { return }
// Deselect previous icon + select new icon // Deselect previous icon + select new icon
collectionView.reloadData() collectionView.reloadData()
// If assigning primary icon, pass "nil" as alternate icon name. // If assigning primary icon, pass "nil" as alternate icon name.
let iconName = (icon.name == AltIcon.defaultName) ? nil : icon.iconName let imageName = (icon.imageName == "AppIcon") ? nil : icon.imageName
UIApplication.shared.setAlternateIconName(iconName) { error in UIApplication.shared.setAlternateIconName(imageName) { error in
if let error if let error
{ {
let alertController = UIAlertController(title: NSLocalizedString("Unable to Change App Icon", comment: ""), let alertController = UIAlertController(title: NSLocalizedString("Unable to Change App Icon", comment: ""),

View File

@@ -380,8 +380,13 @@ private extension ErrorLogViewController
func searchFAQ(for loggedError: LoggedError) func searchFAQ(for loggedError: LoggedError)
{ {
let staticURL = URL(string: "https://docs.sidestore.io/docs/troubleshooting/error-codes")! let baseURL = URL(string: "https://faq.altstore.io/getting-started/error-codes")!
let safariViewController = SFSafariViewController(url: staticURL) 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)
safariViewController.preferredControlTintColor = .altPrimary safariViewController.preferredControlTintColor = .altPrimary
self.present(safariViewController, animated: true) self.present(safariViewController, animated: true)
} }

View File

@@ -42,56 +42,63 @@ struct OperationsLoggingControlView: View {
} }
)) ))
CustomToggle("2. DownloadApp", isOn: Binding( 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(
get: { self.viewModel.getFromDatabase(for: DownloadAppOperation.self) }, get: { self.viewModel.getFromDatabase(for: DownloadAppOperation.self) },
set: { value in set: { value in
self.viewModel.updateDatabase(for: DownloadAppOperation.self, value: value) self.viewModel.updateDatabase(for: DownloadAppOperation.self, value: value)
} }
)) ))
CustomToggle("3. VerifyApp", isOn: Binding( CustomToggle("4. VerifyApp", isOn: Binding(
get: { self.viewModel.getFromDatabase(for: VerifyAppOperation.self) }, get: { self.viewModel.getFromDatabase(for: VerifyAppOperation.self) },
set: { value in set: { value in
self.viewModel.updateDatabase(for: VerifyAppOperation.self, value: value) self.viewModel.updateDatabase(for: VerifyAppOperation.self, value: value)
} }
)) ))
CustomToggle("4. RemoveAppExtensions", isOn: Binding( CustomToggle("5. RemoveAppExtensions", isOn: Binding(
get: { self.viewModel.getFromDatabase(for: RemoveAppExtensionsOperation.self) }, get: { self.viewModel.getFromDatabase(for: RemoveAppExtensionsOperation.self) },
set: { value in set: { value in
self.viewModel.updateDatabase(for: RemoveAppExtensionsOperation.self, value: value) self.viewModel.updateDatabase(for: RemoveAppExtensionsOperation.self, value: value)
} }
)) ))
CustomToggle("5. FetchAnisetteData", isOn: Binding( CustomToggle("6. FetchAnisetteData", isOn: Binding(
get: { self.viewModel.getFromDatabase(for: FetchAnisetteDataOperation.self) }, get: { self.viewModel.getFromDatabase(for: FetchAnisetteDataOperation.self) },
set: { value in set: { value in
self.viewModel.updateDatabase(for: FetchAnisetteDataOperation.self, value: value) self.viewModel.updateDatabase(for: FetchAnisetteDataOperation.self, value: value)
} }
)) ))
CustomToggle("6. FetchProvisioningProfiles(I)", isOn: Binding( CustomToggle("7. FetchProvisioningProfiles(I)", isOn: Binding(
get: { self.viewModel.getFromDatabase(for: FetchProvisioningProfilesInstallOperation.self) }, get: { self.viewModel.getFromDatabase(for: FetchProvisioningProfilesInstallOperation.self) },
set: { value in set: { value in
self.viewModel.updateDatabase(for: FetchProvisioningProfilesInstallOperation.self, value: value) self.viewModel.updateDatabase(for: FetchProvisioningProfilesInstallOperation.self, value: value)
} }
)) ))
CustomToggle("7. ResignApp", isOn: Binding( CustomToggle("8. ResignApp", isOn: Binding(
get: { self.viewModel.getFromDatabase(for: ResignAppOperation.self) }, get: { self.viewModel.getFromDatabase(for: ResignAppOperation.self) },
set: { value in set: { value in
self.viewModel.updateDatabase(for: ResignAppOperation.self, value: value) self.viewModel.updateDatabase(for: ResignAppOperation.self, value: value)
} }
)) ))
CustomToggle("8. SendApp", isOn: Binding( CustomToggle("9. SendApp", isOn: Binding(
get: { self.viewModel.getFromDatabase(for: SendAppOperation.self) }, get: { self.viewModel.getFromDatabase(for: SendAppOperation.self) },
set: { value in set: { value in
self.viewModel.updateDatabase(for: SendAppOperation.self, value: value) self.viewModel.updateDatabase(for: SendAppOperation.self, value: value)
} }
)) ))
CustomToggle("9. InstallApp", isOn: Binding( CustomToggle("10. InstallApp", isOn: Binding(
get: { self.viewModel.getFromDatabase(for: InstallAppOperation.self) }, get: { self.viewModel.getFromDatabase(for: InstallAppOperation.self) },
set: { value in set: { value in
self.viewModel.updateDatabase(for: InstallAppOperation.self, value: value) self.viewModel.updateDatabase(for: InstallAppOperation.self, value: value)
@@ -196,6 +203,16 @@ 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")) CustomSection(header: Text("Cache Operations"))
{ {
CustomToggle("1. ClearAppCache", isOn: Binding( CustomToggle("1. ClearAppCache", isOn: Binding(

View File

@@ -13,18 +13,60 @@ final class PatronCollectionViewCell: UICollectionViewCell
@IBOutlet var textLabel: UILabel! @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 final class AboutPatreonHeaderView: UICollectionReusableView
{ {
@IBOutlet var supportButton: UIButton! @IBOutlet var supportButton: UIButton!
@IBOutlet var twitterButton: UIButton! @IBOutlet var twitterButton: UIButton!
@IBOutlet var instagramButton: UIButton! @IBOutlet var instagramButton: UIButton!
@IBOutlet var accountButton: UIButton!
@IBOutlet var textView: UITextView! @IBOutlet var textView: UITextView!
@IBOutlet private var rileyLabel: UILabel! @IBOutlet private var rileyLabel: UILabel!
@IBOutlet private var shaneLabel: UILabel! @IBOutlet private var shaneLabel: UILabel!
@IBOutlet private var rileyImageView: UIImageView! @IBOutlet private var rileyImageView: UIImageView!
@IBOutlet private var shaneImageView: UIImageView!
override func awakeFromNib() override func awakeFromNib()
{ {
@@ -34,13 +76,13 @@ final class AboutPatreonHeaderView: UICollectionReusableView
self.textView.layer.cornerRadius = 20 self.textView.layer.cornerRadius = 20
self.textView.textContainer.lineFragmentPadding = 0 self.textView.textContainer.lineFragmentPadding = 0
for imageView in [self.rileyImageView].compactMap({$0}) for imageView in [self.rileyImageView, self.shaneImageView].compactMap({$0})
{ {
imageView.clipsToBounds = true imageView.clipsToBounds = true
imageView.layer.cornerRadius = imageView.bounds.midY imageView.layer.cornerRadius = imageView.bounds.midY
} }
for button in [self.supportButton, self.twitterButton, self.instagramButton].compactMap({$0}) for button in [self.supportButton, self.accountButton].compactMap({$0})
{ {
button.clipsToBounds = true button.clipsToBounds = true
button.layer.cornerRadius = 16 button.layer.cornerRadius = 16

View File

@@ -13,10 +13,26 @@ import AuthenticationServices
import AltStoreCore import AltStoreCore
import Roxas import Roxas
extension PatreonViewController
{
private enum Section: Int, CaseIterable
{
case about
case patrons
}
}
final class PatreonViewController: UICollectionViewController final class PatreonViewController: UICollectionViewController
{ {
// private lazy var dataSource = self.makeDataSource()
private lazy var patronsDataSource = self.makePatronsDataSource()
private var prototypeAboutHeader: AboutPatreonHeaderView! private var prototypeAboutHeader: AboutPatreonHeaderView!
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
override func viewDidLoad() override func viewDidLoad()
{ {
super.viewDidLoad() super.viewDidLoad()
@@ -24,14 +40,24 @@ final class PatreonViewController: UICollectionViewController
let aboutHeaderNib = UINib(nibName: "AboutPatreonHeaderView", bundle: nil) let aboutHeaderNib = UINib(nibName: "AboutPatreonHeaderView", bundle: nil)
self.prototypeAboutHeader = aboutHeaderNib.instantiate(withOwner: nil, options: nil)[0] as? AboutPatreonHeaderView 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(aboutHeaderNib, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "AboutHeader")
self.collectionView.reloadData() 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()
} }
override func viewWillAppear(_ animated: Bool) override func viewWillAppear(_ animated: Bool)
{ {
super.viewWillAppear(animated) super.viewWillAppear(animated)
self.collectionView.reloadData()
//self.fetchPatrons()
self.update()
} }
override func viewDidLayoutSubviews() override func viewDidLayoutSubviews()
@@ -50,17 +76,103 @@ final class PatreonViewController: UICollectionViewController
private extension PatreonViewController 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) func prepare(_ headerView: AboutPatreonHeaderView)
{ {
headerView.layoutMargins = self.view.layoutMargins headerView.layoutMargins = self.view.layoutMargins
headerView.supportButton.addTarget(self, action: #selector(PatreonViewController.openPatreonURL(_:)), for: .primaryActionTriggered) headerView.supportButton.addTarget(self, action: #selector(PatreonViewController.openPatreonURL(_:)), for: .primaryActionTriggered)
headerView.twitterButton.addTarget(self, action: #selector(PatreonViewController.openTwitterURL(_:)), for: .primaryActionTriggered) headerView.twitterButton.addTarget(self, action: #selector(PatreonViewController.openTwitterURL(_:)), for: .primaryActionTriggered)
headerView.instagramButton.addTarget(self, action: #selector(PatreonViewController.openInstagramURL(_:)), 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 ,
Youre 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 private extension PatreonViewController
{ {
@objc func fetchPatrons()
{
AppManager.shared.updatePatronsIfNeeded()
self.update()
}
@objc func openPatreonURL(_ sender: UIButton) @objc func openPatreonURL(_ sender: UIButton)
{ {
let patreonURL = URL(string: "https://www.patreon.com/SideStoreIO")! let patreonURL = URL(string: "https://www.patreon.com/SideStoreIO")!
@@ -87,15 +199,148 @@ private extension PatreonViewController
safariViewController.preferredControlTintColor = self.view.tintColor safariViewController.preferredControlTintColor = self.view.tintColor
self.present(safariViewController, animated: true, completion: nil) 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 extension PatreonViewController
{ {
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
{ {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "AboutHeader", for: indexPath) as! AboutPatreonHeaderView let section = Section.allCases[indexPath.section]
self.prepare(headerView) switch section
return headerView {
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
}
}
} }
} }
@@ -103,11 +348,31 @@ extension PatreonViewController: UICollectionViewDelegateFlowLayout
{ {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize
{ {
let widthConstraint = self.prototypeAboutHeader.widthAnchor.constraint(equalToConstant: collectionView.bounds.width) let section = Section.allCases[section]
NSLayoutConstraint.activate([widthConstraint]) switch section
defer { NSLayoutConstraint.deactivate([widthConstraint]) } {
case .about:
let widthConstraint = self.prototypeAboutHeader.widthAnchor.constraint(equalToConstant: collectionView.bounds.width)
NSLayoutConstraint.activate([widthConstraint])
defer { NSLayoutConstraint.deactivate([widthConstraint]) }
self.prepare(self.prototypeAboutHeader) self.prepare(self.prototypeAboutHeader)
return self.prototypeAboutHeader.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
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)
}
} }
} }

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24506" 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="23504" 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"/> <device id="retina6_3" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24504"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Image references" minToolsVersion="12.0"/> <capability name="Image references" minToolsVersion="12.0"/>
<capability name="Named colors" minToolsVersion="9.0"/> <capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
@@ -22,7 +22,7 @@
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<stackView key="tableFooterView" opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalCentering" alignment="center" spacing="15" id="48g-cT-stR"> <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="2442.6666641235352" width="402" height="125"/> <rect key="frame" x="0.0" y="1986.3333377838135" width="402" height="125"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <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"> <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"> <tableViewSection headerTitle="" id="flW-d4-bco">
<cells> <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"> <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="17.666666030883789" width="402" height="51"/> <rect key="frame" x="0.0" y="22.333333969116211" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -134,7 +134,7 @@
<tableViewSection headerTitle="" id="CAI-9J-8fR"> <tableViewSection headerTitle="" id="CAI-9J-8fR">
<cells> <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"> <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="104.33333206176758" width="402" height="51"/> <rect key="frame" x="0.0" y="113.66666793823242" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -166,7 +166,7 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </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"> <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="155.33333206176758" width="402" height="51"/> <rect key="frame" x="0.0" y="164.66666793823242" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -198,7 +198,7 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </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"> <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="206.33333206176758" width="402" height="51"/> <rect key="frame" x="0.0" y="215.66666793823242" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -234,7 +234,7 @@
<tableViewSection id="YHi-gR-wed"> <tableViewSection id="YHi-gR-wed">
<cells> <cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="R1C-Gr-xD4" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="293.33333206176758" width="402" height="51"/> <rect key="frame" x="0.0" y="302.66666793823242" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -275,7 +275,7 @@
<tableViewSection id="RpS-Hn-sQU"> <tableViewSection id="RpS-Hn-sQU">
<cells> <cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="9dU-Hl-NiJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="380.33333206176758" width="402" height="51"/> <rect key="frame" x="0.0" y="389.66666793823242" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -316,7 +316,7 @@
<tableViewSection headerTitle="" id="2dM-lg-cRI"> <tableViewSection headerTitle="" id="2dM-lg-cRI">
<cells> <cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Rra-U5-kCd" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="466.99999809265137" width="402" height="51"/> <rect key="frame" x="0.0" y="481.00000190734863" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -329,7 +329,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DPu-zD-Als"> <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="311" y="11.666666666666664" width="63" height="28"/> <rect key="frame" x="323" y="10" width="51" height="31"/>
<connections> <connections>
<action selector="toggleIsBackgroundRefreshEnabled:" destination="aMk-Xp-UL8" eventType="valueChanged" id="gK5-Wr-8Hh"/> <action selector="toggleIsBackgroundRefreshEnabled:" destination="aMk-Xp-UL8" eventType="valueChanged" id="gK5-Wr-8Hh"/>
</connections> </connections>
@@ -351,7 +351,7 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="GYp-O0-pse" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="GYp-O0-pse" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="517.99999809265137" width="402" height="51"/> <rect key="frame" x="0.0" y="532.00000190734863" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="GYp-O0-pse" id="vDG-ZV-xRS"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -364,7 +364,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iQA-wm-5ag"> <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="311" y="11.666666666666664" width="63" height="28"/> <rect key="frame" x="323" y="10" width="51" height="31"/>
<connections> <connections>
<action selector="toggleNoIdleTimeoutEnabled:" destination="aMk-Xp-UL8" eventType="valueChanged" id="WSl-Jc-g5J"/> <action selector="toggleNoIdleTimeoutEnabled:" destination="aMk-Xp-UL8" eventType="valueChanged" id="WSl-Jc-g5J"/>
</connections> </connections>
@@ -386,7 +386,7 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="amC-sE-8O0" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="amC-sE-8O0" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="568.99999809265137" width="402" height="51"/> <rect key="frame" x="0.0" y="583.00000190734863" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="amC-sE-8O0" id="GEO-2e-E4k"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="amC-sE-8O0" id="GEO-2e-E4k">
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -414,7 +414,7 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="7PQ-AW-GcV" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="619.99999809265137" width="402" height="51"/> <rect key="frame" x="0.0" y="634.00000190734863" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -427,7 +427,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1aa-og-ZXD"> <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="311" y="11.666666666666664" width="63" height="28"/> <rect key="frame" x="323" y="10" width="51" height="31"/>
<connections> <connections>
<action selector="toggleDisableAppLimit:" destination="aMk-Xp-UL8" eventType="valueChanged" id="zYc-B2-JPg"/> <action selector="toggleDisableAppLimit:" destination="aMk-Xp-UL8" eventType="valueChanged" id="zYc-B2-JPg"/>
</connections> </connections>
@@ -453,7 +453,7 @@
<tableViewSection headerTitle="" id="eHy-qI-w5w"> <tableViewSection headerTitle="" id="eHy-qI-w5w">
<cells> <cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="30h-59-88f" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="30h-59-88f" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="706.66666412353516" width="402" height="51"/> <rect key="frame" x="0.0" y="725.33333587646484" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="30h-59-88f" id="7qD-DW-Jls"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="30h-59-88f" id="7qD-DW-Jls">
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -494,7 +494,7 @@
<tableViewSection id="1fc-f1-ALD"> <tableViewSection id="1fc-f1-ALD">
<cells> <cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="7Ek-Ls-QVO" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="793.66666412353516" width="402" height="51"/> <rect key="frame" x="0.0" y="812.33333587646484" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -531,7 +531,7 @@
</connections> </connections>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="hFh-X1-ZAi" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="844.66666412353516" width="402" height="51"/> <rect key="frame" x="0.0" y="863.33333587646484" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -563,7 +563,7 @@
<tableViewSection headerTitle="" id="J90-vn-u2O"> <tableViewSection headerTitle="" id="J90-vn-u2O">
<cells> <cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="931.33333015441895" width="402" height="51"/> <rect key="frame" x="0.0" y="954.66666984558105" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="i4T-2q-jF3" id="VTQ-H4-aCM"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="i4T-2q-jF3" id="VTQ-H4-aCM">
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -608,7 +608,7 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="oHX-oR-nwJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="oHX-oR-nwJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="982.33333015441895" width="402" height="51"/> <rect key="frame" x="0.0" y="1005.6666698455811" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oHX-oR-nwJ" id="hN4-i5-igu"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oHX-oR-nwJ" id="hN4-i5-igu">
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -653,7 +653,7 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="1033.3333301544189" width="402" height="51"/> <rect key="frame" x="0.0" y="1056.6666698455811" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0MT-ht-Sit" id="OZp-WM-5H7"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0MT-ht-Sit" id="OZp-WM-5H7">
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -675,7 +675,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY">
<rect key="frame" x="120.99999999999997" y="-1" width="15.666666666666657" height="22.333333333333332"/> <rect key="frame" x="121" y="-1" width="15.666666666666629" height="22.333333333333332"/>
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/> <imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
</imageView> </imageView>
</subviews> </subviews>
@@ -698,7 +698,7 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="1084.3333301544189" width="402" height="51"/> <rect key="frame" x="0.0" y="1071.6666698455811" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq">
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
@@ -736,98 +736,22 @@
</tableViewCell> </tableViewCell>
</cells> </cells>
</tableViewSection> </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"> <tableViewSection headerTitle="" id="OMa-EK-hRI">
<cells> <cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="1308.6666622161865" width="402" height="51"/> <rect key="frame" x="0.0" y="1163.0000038146973" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<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"> <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">
<rect key="frame" x="29.999999999999993" y="15.333333333333334" width="125.33333333333331" height="20.333333333333329"/> <rect key="frame" x="29.999999999999993" y="15.333333333333334" width="125.33333333333331" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj">
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/> <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"/> <imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
</imageView> </imageView>
@@ -849,19 +773,19 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="1359.6666622161865" width="402" height="51"/> <rect key="frame" x="0.0" y="1214.0000038146973" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<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"> <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">
<rect key="frame" x="30" y="15.333333333333334" width="187.66666666666666" height="20.333333333333329"/> <rect key="frame" x="30" y="15.333333333333334" width="187.66666666666666" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc">
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/> <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"/> <imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
</imageView> </imageView>
@@ -886,19 +810,19 @@
</connections> </connections>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VrV-qI-zXF" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="1410.6666622161865" width="402" height="51"/> <rect key="frame" x="0.0" y="1265.0000038146973" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="SideJITServer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="46q-DB-5nc"> <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">
<rect key="frame" x="29.999999999999993" y="15.333333333333334" width="115.33333333333331" height="20.333333333333329"/> <rect key="frame" x="29.999999999999993" y="15.333333333333334" width="115.33333333333331" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wvD-eZ-nQI"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="wvD-eZ-nQI">
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/> <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"/> <imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
</imageView> </imageView>
@@ -920,19 +844,19 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="1461.6666622161865" width="402" height="51"/> <rect key="frame" x="0.0" y="1316.0000038146973" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Reset 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" 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"/> <rect key="frame" x="30" y="15.333333333333334" width="140" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD">
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/> <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"/> <imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
</imageView> </imageView>
@@ -954,19 +878,19 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="1512.6666622161865" width="402" height="51"/> <rect key="frame" x="0.0" y="1367.0000038146973" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk">
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<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"> <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">
<rect key="frame" x="30" y="15.333333333333334" width="135.66666666666666" height="20.333333333333329"/> <rect key="frame" x="30" y="15.333333333333334" width="135.66666666666666" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0dh-yd-7i9"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="0dh-yd-7i9">
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/> <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"/> <imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
</imageView> </imageView>
@@ -987,31 +911,31 @@
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/> <userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="7rt-MT-kFH" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="1563.6666622161865" width="402" height="51"/> <rect key="frame" x="0.0" y="1418.0000038146973" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="7rt-MT-kFH" id="mZL-UA-6V0"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="XW5-Zc-nXH" id="AtM-bL-8pS">
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<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"> <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="209" height="20.333333333333329"/> <rect key="frame" x="30" y="15.333333333333334" width="169" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8qE-hE-Ujn"> <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="311" y="11.666666666666664" width="63" height="28"/> <rect key="frame" x="323" y="10" width="51" height="31"/>
<connections> <connections>
<action selector="toggleEnableEMPforWireguard:" destination="aMk-Xp-UL8" eventType="valueChanged" id="B0Q-Jb-fox"/> <action selector="toggleEnableBetaUpdates:" destination="aMk-Xp-UL8" eventType="valueChanged" id="uxG-df-7GK"/>
</connections> </connections>
</switch> </switch>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstAttribute="trailingMargin" secondItem="8qE-hE-Ujn" secondAttribute="trailing" id="KgO-N1-7Bw"/> <constraint firstItem="2px-HD-0UT" firstAttribute="centerY" secondItem="AtM-bL-8pS" secondAttribute="centerY" id="07r-jt-3rz"/>
<constraint firstItem="ZH7-ZA-Epf" firstAttribute="leading" secondItem="mZL-UA-6V0" secondAttribute="leadingMargin" id="MBP-lj-8f5"/> <constraint firstItem="2px-HD-0UT" firstAttribute="leading" secondItem="AtM-bL-8pS" secondAttribute="leadingMargin" id="K2i-9G-bG8"/>
<constraint firstItem="ZH7-ZA-Epf" firstAttribute="centerY" secondItem="mZL-UA-6V0" secondAttribute="centerY" id="Pht-1f-5K3"/> <constraint firstAttribute="trailingMargin" secondItem="e32-w4-5fk" secondAttribute="trailing" id="Wa7-m6-lcl"/>
<constraint firstItem="8qE-hE-Ujn" firstAttribute="centerY" secondItem="mZL-UA-6V0" secondAttribute="centerY" id="vhM-Am-Jpo"/> <constraint firstItem="e32-w4-5fk" firstAttribute="centerY" secondItem="AtM-bL-8pS" secondAttribute="centerY" id="n7R-av-FBX"/>
</constraints> </constraints>
</tableViewCellContentView> </tableViewCellContentView>
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -1022,31 +946,33 @@
</userDefinedRuntimeAttribute> </userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="q6e-PG-mTq" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="1614.6666622161865" width="402" height="51"/> <rect key="frame" x="0.0" y="1469.0000038146973" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="q6e-PG-mTq" id="PRJ-Ed-P86"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="qbY-8c-LYT" id="NxK-qB-w7Q">
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<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"> <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="230" height="20.333333333333329"/> <rect key="frame" x="30" y="15.333333333333334" width="159.66666666666666" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="fXx-wl-F5H"> <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="311" y="11.666666666666664" width="63" height="28"/> <rect key="frame" x="301.66666666666669" y="8.3333333333333321" width="70.333333333333314" height="34.333333333333343"/>
<connections> <constraints>
<action selector="toggleEnableAppIdCustomization:" destination="aMk-Xp-UL8" eventType="valueChanged" id="gtP-5M-9Ms"/> <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="50" id="new-width-constraint"/>
</connections> </constraints>
</switch> <buttonConfiguration key="configuration" style="plain" title="stable" titleAlignment="trailing"/>
</button>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstItem="Fq4-2u-Lgd" firstAttribute="centerY" secondItem="PRJ-Ed-P86" secondAttribute="centerY" id="4dM-eV-6Bh"/> <constraint firstItem="5J9-vR-vhX" firstAttribute="centerY" secondItem="NxK-qB-w7Q" secondAttribute="centerY" id="5gk-uR-bun"/>
<constraint firstItem="Fq4-2u-Lgd" firstAttribute="leading" secondItem="PRJ-Ed-P86" secondAttribute="leadingMargin" id="Pd8-US-Y4U"/> <constraint firstAttribute="trailingMargin" secondItem="Oct-iT-NwP" secondAttribute="trailing" id="Wa8-m6-lcl"/>
<constraint firstAttribute="trailingMargin" secondItem="fXx-wl-F5H" secondAttribute="trailing" id="ZU9-iK-2ox"/> <constraint firstItem="5J9-vR-vhX" firstAttribute="leading" secondItem="NxK-qB-w7Q" secondAttribute="leadingMargin" id="af8-XD-RSn"/>
<constraint firstItem="fXx-wl-F5H" firstAttribute="centerY" secondItem="PRJ-Ed-P86" secondAttribute="centerY" id="fhs-rv-x3J"/> <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"/>
</constraints> </constraints>
</tableViewCellContentView> </tableViewCellContentView>
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -1059,139 +985,23 @@
</tableViewCell> </tableViewCell>
</cells> </cells>
</tableViewSection> </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"> <tableViewSection headerTitle="" id="lLQ-K0-XSb">
<cells> <cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="daQ-mk-yqC" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="1941.3333282470703" width="402" height="51"/> <rect key="frame" x="0.0" y="1560.3333377838135" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<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"> <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">
<rect key="frame" x="30.000000000000014" y="15.333333333333334" width="215.33333333333337" height="20.333333333333329"/> <rect key="frame" x="30.000000000000014" y="15.333333333333334" width="215.33333333333337" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AAh-cu-qw8"> <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="311" y="11.666666666666664" width="63" height="28"/> <rect key="frame" x="323" y="10" width="51" height="31"/>
<connections> <connections>
<action selector="toggleDisableResponseCaching:" destination="aMk-Xp-UL8" eventType="valueChanged" id="lCm-qi-piH"/> <action selector="toggleDisableResponseCaching:" destination="aMk-Xp-UL8" eventType="valueChanged" id="lCm-qi-piH"/>
</connections> </connections>
@@ -1213,20 +1023,20 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="hRP-jU-2hd" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="1992.3333282470703" width="402" height="51"/> <rect key="frame" x="0.0" y="1611.3333377838135" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<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"> <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">
<rect key="frame" x="30" y="15.333333333333334" width="180" height="20.333333333333329"/> <rect key="frame" x="30" y="15.333333333333334" width="180" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="GYP-qn-wzh"> <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="311" y="11.666666666666664" width="63" height="28"/> <rect key="frame" x="323" y="10" width="51" height="31"/>
<connections> <connections>
<action selector="toggleResignedAppExport:" destination="aMk-Xp-UL8" eventType="valueChanged" id="Z1k-xh-sjD"/> <action selector="toggleResignedAppExport:" destination="aMk-Xp-UL8" eventType="valueChanged" id="Z1k-xh-sjD"/>
</connections> </connections>
@@ -1248,20 +1058,20 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="JoN-Aj-XtZ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="2043.3333282470703" width="402" height="51"/> <rect key="frame" x="0.0" y="1662.3333377838135" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<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"> <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">
<rect key="frame" x="29.999999999999986" y="15.333333333333334" width="232.66666666666663" height="20.333333333333329"/> <rect key="frame" x="29.999999999999986" y="15.333333333333334" width="232.66666666666663" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" ambiguous="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Q5X-Mo-KpE"> <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="311" y="11.666666666666664" width="63" height="28"/> <rect key="frame" x="323" y="10" width="51" height="31"/>
<connections> <connections>
<action selector="toggleVerboseOperationsLogging:" destination="aMk-Xp-UL8" eventType="valueChanged" id="n9N-Gt-OY2"/> <action selector="toggleVerboseOperationsLogging:" destination="aMk-Xp-UL8" eventType="valueChanged" id="n9N-Gt-OY2"/>
</connections> </connections>
@@ -1283,13 +1093,13 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="QOO-bO-4M5" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="2094.3333282470703" width="402" height="51"/> <rect key="frame" x="0.0" y="1713.3333377838135" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<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"> <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">
<rect key="frame" x="30" y="15.333333333333334" width="151.66666666666666" height="20.333333333333329"/> <rect key="frame" x="30" y="15.333333333333334" width="151.66666666666666" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -1311,13 +1121,13 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="ToB-H7-2lR" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="2145.3333282470703" width="402" height="51"/> <rect key="frame" x="0.0" y="1764.3333377838135" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<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"> <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">
<rect key="frame" x="30" y="15.333333333333334" width="150.33333333333334" height="20.333333333333329"/> <rect key="frame" x="30" y="15.333333333333334" width="150.33333333333334" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -1339,19 +1149,19 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="xtI-eU-LFb" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="2196.3333282470703" width="402" height="51"/> <rect key="frame" x="0.0" y="1815.3333377838135" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<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"> <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">
<rect key="frame" x="30.000000000000014" y="15.333333333333334" width="224.33333333333337" height="20.333333333333329"/> <rect key="frame" x="30.000000000000014" y="15.333333333333334" width="224.33333333333337" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zl4-ti-HTW"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="zl4-ti-HTW">
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/> <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"/> <imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
</imageView> </imageView>
@@ -1373,20 +1183,20 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="pvu-IV-Poa" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="2247.3333282470703" width="402" height="51"/> <rect key="frame" x="0.0" y="1866.3333377838135" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<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"> <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">
<rect key="frame" x="30" y="15.333333333333334" width="265.33333333333331" height="20.333333333333329"/> <rect key="frame" x="30" y="15.333333333333334" width="265.33333333333331" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<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"> <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="311" y="11.666666666666664" width="63" height="28"/> <rect key="frame" x="323" y="10" width="51" height="31"/>
<connections> <connections>
<action selector="toggleRecreateDatabaseSwitch:" destination="aMk-Xp-UL8" eventType="valueChanged" id="vlf-Iz-kWr"/> <action selector="toggleRecreateDatabaseSwitch:" destination="aMk-Xp-UL8" eventType="valueChanged" id="vlf-Iz-kWr"/>
</connections> </connections>
@@ -1408,20 +1218,20 @@
</userDefinedRuntimeAttributes> </userDefinedRuntimeAttributes>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="9By-QW-Jw9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target"> <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="2298.3333282470703" width="402" height="51"/> <rect key="frame" x="0.0" y="1917.3333377838135" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<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"> <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">
<rect key="frame" x="30.000000000000014" y="15.333333333333334" width="225.33333333333337" height="20.333333333333329"/> <rect key="frame" x="30.000000000000014" y="15.333333333333334" width="225.33333333333337" height="20.333333333333329"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<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"> <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="311" y="11.666666666666664" width="63" height="28"/> <rect key="frame" x="323" y="10" width="51" height="31"/>
<connections> <connections>
<action selector="toggleMinimuxerConsoleLogging:" destination="aMk-Xp-UL8" eventType="valueChanged" id="d0C-kx-aFV"/> <action selector="toggleMinimuxerConsoleLogging:" destination="aMk-Xp-UL8" eventType="valueChanged" id="d0C-kx-aFV"/>
</connections> </connections>
@@ -1436,41 +1246,6 @@
</tableViewCellContentView> </tableViewCellContentView>
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/> <edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="style">
<integer key="value" value="2"/>
</userDefinedRuntimeAttribute>
</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> <userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="style"> <userDefinedRuntimeAttribute type="number" keyPath="style">
<integer key="value" value="3"/> <integer key="value" value="3"/>
@@ -1491,13 +1266,11 @@
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/> <outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/> <outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/> <outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
<outlet property="betaTrackLabel" destination="1B5-BJ-Rkb" id="wcZ-in-EOa"/> <outlet property="betaTrackLabel" destination="5J9-vR-vhX" id="x2k-X7-pUr"/>
<outlet property="betaTrackPopupButton" destination="kYQ-zz-vjQ" id="QXm-ud-70m"/> <outlet property="betaTrackPopupButton" destination="Oct-iT-NwP" id="sOR-cc-IWC"/>
<outlet property="betaUpdatesSwitch" destination="Blb-Dp-9QF" id="C4e-rj-ocb"/> <outlet property="betaUpdatesSwitch" destination="e32-w4-5fk" id="kdn-ZR-cNU"/>
<outlet property="customizeAppIdSwitch" destination="fXx-wl-F5H" id="Yc0-ZL-aDs"/>
<outlet property="disableAppLimitSwitch" destination="1aa-og-ZXD" id="oVL-Md-yZ8"/> <outlet property="disableAppLimitSwitch" destination="1aa-og-ZXD" id="oVL-Md-yZ8"/>
<outlet property="disableResponseCachingSwitch" destination="AAh-cu-qw8" id="aVT-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="exportResignedAppsSwitch" destination="GYP-qn-wzh" id="aVL-Md-yZ8"/>
<outlet property="githubButton" destination="oqj-4S-I9l" id="sxB-LE-gA2"/> <outlet property="githubButton" destination="oqj-4S-I9l" id="sxB-LE-gA2"/>
<outlet property="mastodonButton" destination="B8Q-e7-beR" id="Kbe-Og-rsg"/> <outlet property="mastodonButton" destination="B8Q-e7-beR" id="Kbe-Og-rsg"/>
@@ -1522,9 +1295,8 @@
<toolbarItems/> <toolbarItems/>
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/> <simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Jtn-cs-Tvp" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target"> <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Jtn-cs-Tvp" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="124" width="402" height="106"/> <rect key="frame" x="0.0" y="62" width="402" height="96"/>
<autoresizingMask key="autoresizingMask"/> <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="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="barTintColor" name="SettingsBackground"/> <color key="barTintColor" name="SettingsBackground"/>
<textAttributes key="titleTextAttributes"> <textAttributes key="titleTextAttributes">
@@ -1556,14 +1328,14 @@
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<prototypes> <prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="8Xf-RE-QJx" customClass="RefreshAttemptTableViewCell"> <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="73.333335876464844"/> <rect key="frame" x="0.0" y="50" width="402" height="64.666664123535156"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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="73.333335876464844"/> <rect key="frame" x="0.0" y="0.0" width="402" height="64.666664123535156"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="SN9-pA-GDU"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="SN9-pA-GDU">
<rect key="frame" x="20" y="15.000000000000004" width="362" height="43.333333333333343"/> <rect key="frame" x="20" y="10.999999999999996" width="362" height="42.666666666666657"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="SqJ-wP-gO1"> <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"/> <rect key="frame" x="0.0" y="0.0" width="362" height="20.333333333333332"/>
@@ -1583,7 +1355,7 @@
</subviews> </subviews>
</stackView> </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"> <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="19"/> <rect key="frame" x="0.0" y="24.333333333333336" width="362" height="18.333333333333336"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/> <fontDescription key="fontDescription" type="system" pointSize="15"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
@@ -1624,7 +1396,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" layoutMarginsFollowReadableWidth="YES" contentInsetAdjustmentBehavior="never" indicatorStyle="white" editable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oQQ-pR-oKc"> <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" layoutMarginsFollowReadableWidth="YES" contentInsetAdjustmentBehavior="never" indicatorStyle="white" editable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oQQ-pR-oKc">
<rect key="frame" x="0.0" y="178" width="402" height="579"/> <rect key="frame" x="0.0" y="106" width="402" height="685"/>
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/> <edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
<string key="text">Jay Freeman (ldid) <string key="text">Jay Freeman (ldid)
Copyright (C) 2007-2012 Jay Freeman (saurik) Copyright (C) 2007-2012 Jay Freeman (saurik)
@@ -1768,7 +1540,33 @@ Settings by i cons from the Noun Project</string>
<size key="footerReferenceSize" width="0.0" height="0.0"/> <size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="20" minY="8" maxX="20" maxY="0.0"/> <inset key="sectionInset" minX="20" minY="8" maxX="20" maxY="0.0"/>
</collectionViewFlowLayout> </collectionViewFlowLayout>
<cells/> <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>
<connections> <connections>
<outlet property="dataSource" destination="dp8-8j-vt9" id="ONG-kb-M7N"/> <outlet property="dataSource" destination="dp8-8j-vt9" id="ONG-kb-M7N"/>
<outlet property="delegate" destination="dp8-8j-vt9" id="790-Kr-6l7"/> <outlet property="delegate" destination="dp8-8j-vt9" id="790-Kr-6l7"/>
@@ -1797,17 +1595,17 @@ Settings by i cons from the Noun Project</string>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<prototypes> <prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" id="HAm-mA-O78" customClass="ErrorLogTableViewCell"> <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="116"/> <rect key="frame" x="20" y="55.333332061767578" width="362" height="107.33333587646484"/>
<autoresizingMask key="autoresizingMask"/> <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"> <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="116"/> <rect key="frame" x="0.0" y="0.0" width="362" height="107.33333587646484"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="mtw-JM-T70"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="mtw-JM-T70">
<rect key="frame" x="16" y="15" width="330" height="86"/> <rect key="frame" x="20" y="11" width="322" height="85.333333333333329"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" alignment="top" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="bjU-TX-4lm" userLabel="Compact"> <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="330" height="44"/> <rect key="frame" x="0.0" y="0.0" width="322" height="43.333333333333336"/>
<subviews> <subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="sDZ-ZN-NT1" customClass="AppIconImageView" customModule="SideStore" customModuleProvider="target"> <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"/> <rect key="frame" x="0.0" y="0.0" width="43" height="43"/>
@@ -1817,10 +1615,10 @@ Settings by i cons from the Noun Project</string>
</constraints> </constraints>
</imageView> </imageView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="82d-v0-RCp"> <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="279" height="39"/> <rect key="frame" x="51" y="0.0" width="271" height="39"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="Q2j-Tc-bp2"> <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="279" height="18"/> <rect key="frame" x="0.0" y="0.0" width="271" height="18"/>
<subviews> <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"> <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"/> <rect key="frame" x="0.0" y="0.0" width="60" height="18"/>
@@ -1829,7 +1627,7 @@ Settings by i cons from the Noun Project</string>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </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"> <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="248.66666666666671" y="0.0" width="30.333333333333343" height="18"/> <rect key="frame" x="240.66666666666669" y="0.0" width="30.333333333333314" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -1837,7 +1635,7 @@ Settings by i cons from the Noun Project</string>
</subviews> </subviews>
</stackView> </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"> <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="279" height="17"/> <rect key="frame" x="0.0" y="22" width="271" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -1847,7 +1645,7 @@ Settings by i cons from the Noun Project</string>
</subviews> </subviews>
</stackView> </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"> <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="52" width="330" height="34"/> <rect key="frame" x="0.0" y="51.333333333333343" width="322" height="34"/>
<accessibility key="accessibilityConfiguration"> <accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" staticText="YES"/> <accessibilityTraits key="traits" staticText="YES"/>
<bool key="isElement" value="NO"/> <bool key="isElement" value="NO"/>
@@ -1859,7 +1657,7 @@ Settings by i cons from the Noun Project</string>
</subviews> </subviews>
</stackView> </stackView>
<button opaque="NO" contentMode="scaleToFill" showsMenuAsPrimaryAction="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ba2-EY-tf5" customClass="ErrorLogMenuButton"> <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="116"/> <rect key="frame" x="0.0" y="0.0" width="362" height="107.33333333333333"/>
<accessibility key="accessibilityConfiguration"> <accessibility key="accessibilityConfiguration">
<bool key="isElement" value="NO"/> <bool key="isElement" value="NO"/>
</accessibility> </accessibility>
@@ -1930,11 +1728,11 @@ Settings by i cons from the Noun Project</string>
<objects> <objects>
<viewController id="xB2-Se-VVg" customClass="ErrorDetailsViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController"> <viewController id="xB2-Se-VVg" customClass="ErrorDetailsViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="eBQ-se-VIy"> <view key="view" contentMode="scaleToFill" id="eBQ-se-VIy">
<rect key="frame" x="0.0" y="0.0" width="402" height="812"/> <rect key="frame" x="0.0" y="0.0" width="402" height="864"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="ctd-NB-4ov"> <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="812"/> <rect key="frame" x="0.0" y="0.0" width="402" height="864"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<color key="textColor" systemColor="labelColor"/> <color key="textColor" systemColor="labelColor"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/> <fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
@@ -1972,9 +1770,8 @@ Settings by i cons from the Noun Project</string>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="7gm-d1-zWK" sceneMemberID="viewController"> <navigationController automaticallyAdjustsScrollViewInsets="NO" id="7gm-d1-zWK" sceneMemberID="viewController">
<toolbarItems/> <toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" id="dI0-sh-yGf"> <navigationBar key="navigationBar" contentMode="scaleToFill" id="dI0-sh-yGf">
<rect key="frame" x="0.0" y="16" width="402" height="54"/> <rect key="frame" x="0.0" y="0.0" width="402" height="56"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
<connections> <connections>

View File

@@ -16,12 +16,10 @@ import IntentsUI
import SemanticVersion import SemanticVersion
import AltStoreCore import AltStoreCore
import CAltSign
import UniformTypeIdentifiers
extension SettingsViewController extension SettingsViewController
{ {
private enum Section: Int, CaseIterable fileprivate enum Section: Int, CaseIterable
{ {
case signIn case signIn
case account case account
@@ -31,14 +29,13 @@ extension SettingsViewController
case instructions case instructions
case techyThings case techyThings
case credits case credits
case betaTesting
case advancedSettings case advancedSettings
case signing // diagnostics section, will be enabled on release builds only on swipe down with 3 fingers 3 times
case diagnostics // diagnostics section, will be enabled on release builds only on swipe down with 3 fingers 3 times case diagnostics
// case macDirtyCow // case macDirtyCow
} }
private enum AppRefreshRow: Int, CaseIterable fileprivate enum AppRefreshRow: Int, CaseIterable
{ {
case backgroundRefresh case backgroundRefresh
case noIdleTimeout case noIdleTimeout
@@ -57,7 +54,7 @@ extension SettingsViewController
} }
} }
private enum CreditsRow: Int, CaseIterable fileprivate enum CreditsRow: Int, CaseIterable
{ {
case developer case developer
case operations case operations
@@ -65,36 +62,25 @@ extension SettingsViewController
case softwareLicenses case softwareLicenses
} }
private enum TechyThingsRow: Int, CaseIterable fileprivate enum TechyThingsRow: Int, CaseIterable
{ {
case errorLog case errorLog
case clearCache case clearCache
} }
private enum AdvancedSettingsRow: Int, CaseIterable fileprivate enum AdvancedSettingsRow: Int, CaseIterable
{ {
case sendFeedback case sendFeedback
case refreshAttempts case refreshAttempts
case refreshSideJITServer case refreshSideJITServer
case resetPairingFile case resetPairingFile
case anisetteServers case anisetteServers
case enableEMPForWiregaurd
case customizeAppId
}
private enum SigningSettingsRow: Int, CaseIterable {
case importAccount
case exportAccount
case importCert
case exportCert
}
private enum BetaTestingRow: Int, CaseIterable {
case betaUpdates case betaUpdates
case betaTrack case betaTrack
// case hiddenSettings
} }
private enum DiagnosticsRow: Int, CaseIterable fileprivate enum DiagnosticsRow: Int, CaseIterable
{ {
case responseCaching case responseCaching
case exportResignedApp case exportResignedApp
@@ -125,11 +111,9 @@ final class SettingsViewController: UITableViewController
@IBOutlet private var accountTypeLabel: UILabel! @IBOutlet private var accountTypeLabel: UILabel!
@IBOutlet private var backgroundRefreshSwitch: UISwitch! @IBOutlet private var backgroundRefreshSwitch: UISwitch!
@IBOutlet private var enableEMPforWireguard: UISwitch!
@IBOutlet private var noIdleTimeoutSwitch: UISwitch! @IBOutlet private var noIdleTimeoutSwitch: UISwitch!
@IBOutlet private var disableAppLimitSwitch: UISwitch! @IBOutlet private var disableAppLimitSwitch: UISwitch!
@IBOutlet private var betaUpdatesSwitch: UISwitch! @IBOutlet private var betaUpdatesSwitch: UISwitch!
@IBOutlet private var customizeAppIdSwitch: UISwitch!
@IBOutlet private var exportResignedAppsSwitch: UISwitch! @IBOutlet private var exportResignedAppsSwitch: UISwitch!
@IBOutlet private var verboseOperationsLoggingSwitch: UISwitch! @IBOutlet private var verboseOperationsLoggingSwitch: UISwitch!
@IBOutlet private var minimuxerConsoleLoggingSwitch: UISwitch! @IBOutlet private var minimuxerConsoleLoggingSwitch: UISwitch!
@@ -159,7 +143,6 @@ final class SettingsViewController: UITableViewController
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openErrorLog(_:)), name: ToastView.openErrorLogNotification, object: nil) 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)
} }
@@ -208,16 +191,6 @@ final class SettingsViewController: UITableViewController
{ {
super.viewDidLoad() 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) let nib = UINib(nibName: "SettingsHeaderFooterView", bundle: nil)
self.prototypeHeaderFooterView = nib.instantiate(withOwner: nil, options: nil)[0] as? SettingsHeaderFooterView self.prototypeHeaderFooterView = nib.instantiate(withOwner: nil, options: nil)[0] as? SettingsHeaderFooterView
@@ -263,118 +236,6 @@ final class SettingsViewController: UITableViewController
} }
configureReleaseChannelButton() 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) override func viewWillAppear(_ animated: Bool)
@@ -402,6 +263,50 @@ final class SettingsViewController: UITableViewController
} }
private class BuildInfo{
private static let MARKETING_VERSION_TAG = "CFBundleShortVersionString"
private static let CURRENT_PROJECT_VERSION_TAG = kCFBundleVersionKey as String
private static let XCODE_VERSION_TAG = "DTXcode"
private static let XCODE_REVISION_TAG = "DTXcodeBuild"
let bundle: Bundle
public init(){
bundle = Bundle.main
}
enum BundleError: Swift.Error {
case invalidURL
}
public init(url: URL) throws {
guard let bundle = Bundle(url: url) else {
throw BundleError.invalidURL
}
self.bundle = bundle
}
public lazy var project_version: String? = {
let version = bundle.object(forInfoDictionaryKey: Self.CURRENT_PROJECT_VERSION_TAG) as? String
return version
}()
public lazy var marketing_version: String? = {
let version = bundle.object(forInfoDictionaryKey: Self.MARKETING_VERSION_TAG) as? String
return version
}()
public lazy var xcode: String? = {
let xcode = bundle.object(forInfoDictionaryKey: Self.XCODE_VERSION_TAG) as? String
return xcode
}()
public lazy var xcode_revision: String? = {
let revision = bundle.object(forInfoDictionaryKey: Self.XCODE_REVISION_TAG) as? String
return revision
}()
}
private extension SettingsViewController private extension SettingsViewController
{ {
@@ -474,15 +379,12 @@ private extension SettingsViewController
// AppRefreshRow // AppRefreshRow
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
self.enableEMPforWireguard.isOn = UserDefaults.standard.enableEMPforWireguard
self.noIdleTimeoutSwitch.isOn = UserDefaults.standard.isIdleTimeoutDisableEnabled self.noIdleTimeoutSwitch.isOn = UserDefaults.standard.isIdleTimeoutDisableEnabled
self.disableAppLimitSwitch.isOn = UserDefaults.standard.isAppLimitDisabled self.disableAppLimitSwitch.isOn = UserDefaults.standard.isAppLimitDisabled
// AdvancedSettingsRow // AdvancedSettingsRow
self.customizeAppIdSwitch.isOn = UserDefaults.standard.customizeAppId
// BetaTestingRow
self.betaUpdatesSwitch.isOn = UserDefaults.standard.isBetaUpdatesEnabled self.betaUpdatesSwitch.isOn = UserDefaults.standard.isBetaUpdatesEnabled
self.betaTrackLabel.isEnabled = UserDefaults.standard.isBetaUpdatesEnabled
self.betaTrackPopupButton.isEnabled = UserDefaults.standard.isBetaUpdatesEnabled self.betaTrackPopupButton.isEnabled = UserDefaults.standard.isBetaUpdatesEnabled
// DiagnosticsRow // DiagnosticsRow
@@ -499,7 +401,7 @@ private extension SettingsViewController
} }
} }
private func prepare(_ settingsHeaderFooterView: SettingsHeaderFooterView, for section: Section, isHeader: Bool) func prepare(_ settingsHeaderFooterView: SettingsHeaderFooterView, for section: Section, isHeader: Bool)
{ {
settingsHeaderFooterView.primaryLabel.isHidden = !isHeader settingsHeaderFooterView.primaryLabel.isHidden = !isHeader
settingsHeaderFooterView.secondaryLabel.isHidden = isHeader settingsHeaderFooterView.secondaryLabel.isHidden = isHeader
@@ -576,34 +478,6 @@ private extension SettingsViewController
case .advancedSettings: case .advancedSettings:
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("ADVANCED SETTINGS", comment: "") 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: case .diagnostics:
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("DIAGNOSTICS", comment: "") settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("DIAGNOSTICS", comment: "")
@@ -620,7 +494,7 @@ private extension SettingsViewController
} }
} }
private func preferredHeight(for settingsHeaderFooterView: SettingsHeaderFooterView, in section: Section, isHeader: Bool) -> CGFloat func preferredHeight(for settingsHeaderFooterView: SettingsHeaderFooterView, in section: Section, isHeader: Bool) -> CGFloat
{ {
let widthConstraint = settingsHeaderFooterView.contentView.widthAnchor.constraint(equalToConstant: tableView.bounds.width) let widthConstraint = settingsHeaderFooterView.contentView.widthAnchor.constraint(equalToConstant: tableView.bounds.width)
NSLayoutConstraint.activate([widthConstraint]) NSLayoutConstraint.activate([widthConstraint])
@@ -632,7 +506,7 @@ private extension SettingsViewController
return size.height return size.height
} }
private func isSectionHidden(_ section: Section) -> Bool func isSectionHidden(_ section: Section) -> Bool
{ {
switch section switch section
{ {
@@ -723,11 +597,6 @@ private extension SettingsViewController
UserDefaults.standard.isMinimuxerConsoleLoggingEnabled = sender.isOn UserDefaults.standard.isMinimuxerConsoleLoggingEnabled = sender.isOn
} }
@IBAction func toggleMinimuxerStatusCheck(_ sender: UISwitch) {
// update it in database
UserDefaults.standard.isMinimuxerStatusCheckEnabled = sender.isOn
}
@IBAction func toggleRecreateDatabaseSwitch(_ sender: UISwitch) { @IBAction func toggleRecreateDatabaseSwitch(_ sender: UISwitch) {
// Update the setting in UserDefaults // Update the setting in UserDefaults
UserDefaults.standard.recreateDatabaseOnNextStart = sender.isOn UserDefaults.standard.recreateDatabaseOnNextStart = sender.isOn
@@ -765,21 +634,11 @@ private extension SettingsViewController
UserDefaults.standard.isBetaUpdatesEnabled = sender.isOn UserDefaults.standard.isBetaUpdatesEnabled = sender.isOn
} }
@IBAction func toggleEnableAppIdCustomization(_ sender: UISwitch) {
// update it in database
UserDefaults.standard.customizeAppId = sender.isOn
}
@IBAction func toggleIsBackgroundRefreshEnabled(_ sender: UISwitch) @IBAction func toggleIsBackgroundRefreshEnabled(_ sender: UISwitch)
{ {
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
} }
@IBAction func toggleEnableEMPforWireguard(_ sender: UISwitch)
{
UserDefaults.standard.enableEMPforWireguard = sender.isOn
}
@IBAction func toggleNoIdleTimeoutEnabled(_ sender: UISwitch) @IBAction func toggleNoIdleTimeoutEnabled(_ sender: UISwitch)
{ {
UserDefaults.standard.isIdleTimeoutDisableEnabled = sender.isOn UserDefaults.standard.isIdleTimeoutDisableEnabled = sender.isOn
@@ -941,48 +800,6 @@ private extension SettingsViewController
self.performSegue(withIdentifier: "showErrorLog", sender: nil) 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 extension SettingsViewController
@@ -1054,7 +871,7 @@ extension SettingsViewController
case _ where isSectionHidden(section): return nil case _ where isSectionHidden(section): return nil
case .signIn where self.activeTeam != nil: return nil case .signIn where self.activeTeam != nil: return nil
case .account where self.activeTeam == nil: return nil case .account where self.activeTeam == nil: return nil
case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .signing, .betaTesting, .diagnostics /* ,.macDirtyCow */: case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .diagnostics /* ,.macDirtyCow */:
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
self.prepare(headerView, for: section, isHeader: true) self.prepare(headerView, for: section, isHeader: true)
return headerView return headerView
@@ -1071,7 +888,7 @@ extension SettingsViewController
case _ where isSectionHidden(section): return nil case _ where isSectionHidden(section): return nil
case .signIn where self.activeTeam != nil: return nil case .signIn where self.activeTeam != nil: return nil
// case .signIn, .patreon, .display, .appRefresh, .techyThings, .macDirtyCow: // case .signIn, .patreon, .display, .appRefresh, .techyThings, .macDirtyCow:
case .signIn, .patreon, .display, .appRefresh, .techyThings, .signing, .betaTesting: case .signIn, .patreon, .display, .appRefresh, .techyThings:
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
self.prepare(footerView, for: section, isHeader: false) self.prepare(footerView, for: section, isHeader: false)
return footerView return footerView
@@ -1088,7 +905,8 @@ extension SettingsViewController
case _ where isSectionHidden(section): return 1.0 case _ where isSectionHidden(section): return 1.0
case .signIn where self.activeTeam != nil: return 1.0 case .signIn where self.activeTeam != nil: return 1.0
case .account where self.activeTeam == nil: return 1.0 case .account where self.activeTeam == nil: return 1.0
case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .signing, .betaTesting, .diagnostics: // case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .macDirtyCow, .advanced:
case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .diagnostics:
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true) let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true)
return height return height
@@ -1105,7 +923,7 @@ extension SettingsViewController
case .signIn where self.activeTeam != nil: return 1.0 case .signIn where self.activeTeam != nil: return 1.0
case .account where self.activeTeam == nil: return 1.0 case .account where self.activeTeam == nil: return 1.0
// case .signIn, .patreon, .display, .appRefresh, .techyThings, .macDirtyCow: // case .signIn, .patreon, .display, .appRefresh, .techyThings, .macDirtyCow:
case .signIn, .patreon, .display, .appRefresh, .techyThings, .signing, .diagnostics, .betaTesting: case .signIn, .patreon, .display, .appRefresh, .techyThings, .diagnostics:
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false) let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
return height return height
@@ -1175,7 +993,7 @@ extension SettingsViewController
// Option 2: Discord // Option 2: Discord
alertController.addAction(UIAlertAction(title: "Discord", style: .default) { _ in alertController.addAction(UIAlertAction(title: "Discord", style: .default) { _ in
if let discordURL = URL(string: "https://discord.gg/sidestore-949183273383395328") { if let discordURL = URL(string: "https://discord.gg/sidestore") {
let safariViewController = SFSafariViewController(url: discordURL) let safariViewController = SFSafariViewController(url: discordURL)
safariViewController.preferredControlTintColor = .altPrimary safariViewController.preferredControlTintColor = .altPrimary
self.present(safariViewController, animated: true, completion: nil) self.present(safariViewController, animated: true, completion: nil)
@@ -1368,162 +1186,17 @@ extension SettingsViewController
let anisetteServersController = UIHostingController(rootView: anisetteServersView) let anisetteServersController = UIHostingController(rootView: anisetteServersView)
self.prepare(for: UIStoryboardSegue(identifier: "anisetteServers", source: self, destination: anisetteServersController), sender: nil) self.prepare(for: UIStoryboardSegue(identifier: "anisetteServers", source: self, destination: anisetteServersController), sender: nil)
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) // 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
}
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: case .diagnostics:
@@ -1577,7 +1250,7 @@ extension SettingsViewController
// case .account, .patreon, .display, .instructions, .macDirtyCow: break // case .account, .patreon, .display, .instructions, .macDirtyCow: break
case .account, .patreon, .display, .instructions, .betaTesting: break case .account, .patreon, .display, .instructions: break
} }

View File

@@ -20,7 +20,6 @@
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="xWh-1U-u0q" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target"> <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"/> <rect key="frame" x="0.0" y="59" width="393" height="96"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
<connections> <connections>
@@ -249,7 +248,6 @@
<navigationBar key="navigationBar" contentMode="scaleToFill" largeTitles="YES" id="HLe-3g-P8I" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target"> <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"/> <rect key="frame" x="0.0" y="0.0" width="393" height="108"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
</navigationBar> </navigationBar>
<nil name="viewControllers"/> <nil name="viewControllers"/>
<connections> <connections>

View File

@@ -46,14 +46,6 @@ final class SourcesViewController: UICollectionViewController
{ {
super.viewDidLoad() 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() let layout = self.makeLayout()
self.collectionView.collectionViewLayout = layout self.collectionView.collectionViewLayout = layout
@@ -522,27 +514,27 @@ extension SourcesViewController: NSFetchedResultsControllerDelegate
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext() let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
context.performAndWait { context.performAndWait {
_ = Source.make(name: "OatmealDome's AltStore Source", _ = Source.make(name: "OatmealDome's AltStore Source",
groupID: "me.oatmealdome.altstore", identifier: "me.oatmealdome.altstore",
sourceURL: URL(string: "https://altstore.oatmealdome.me")!, sourceURL: URL(string: "https://altstore.oatmealdome.me")!,
context: context) context: context)
_ = Source.make(name: "UTM Repository", _ = Source.make(name: "UTM Repository",
groupID: "com.utmapp.repos.UTM", identifier: "com.utmapp.repos.UTM",
sourceURL: URL(string: "https://alt.getutm.app")!, sourceURL: URL(string: "https://alt.getutm.app")!,
context: context) context: context)
_ = Source.make(name: "Flyinghead", _ = Source.make(name: "Flyinghead",
groupID: "com.flyinghead.source", identifier: "com.flyinghead.source",
sourceURL: URL(string: "https://flyinghead.github.io/flycast-builds/altstore.json")!, sourceURL: URL(string: "https://flyinghead.github.io/flycast-builds/altstore.json")!,
context: context) context: context)
_ = Source.make(name: "Provenance", _ = Source.make(name: "Provenance",
groupID: "org.provenance-emu.AltStore", identifier: "org.provenance-emu.AltStore",
sourceURL: URL(string: "https://provenance-emu.com/apps.json")!, sourceURL: URL(string: "https://provenance-emu.com/apps.json")!,
context: context) context: context)
_ = Source.make(name: "PojavLauncher Repository", _ = Source.make(name: "PojavLauncher Repository",
groupID: "dev.crystall1ne.repos.PojavLauncher", identifier: "dev.crystall1ne.repos.PojavLauncher",
sourceURL: URL(string: "http://alt.crystall1ne.dev")!, sourceURL: URL(string: "http://alt.crystall1ne.dev")!,
context: context) context: context)

View File

@@ -36,7 +36,6 @@ final class TabBarController: UITabBarController
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.importApp(_:)), name: AppDelegate.importAppDeepLinkNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.importApp(_:)), name: AppDelegate.importAppDeepLinkNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.presentSources(_:)), name: AppDelegate.addSourceDeepLinkNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.presentSources(_:)), name: AppDelegate.addSourceDeepLinkNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.exportFiles(_:)), name: AppDelegate.exportCertificateNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openErrorLog(_:)), name: ToastView.openErrorLogNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openErrorLog(_:)), name: ToastView.openErrorLogNotification, object: nil)
} }
@@ -142,9 +141,4 @@ private extension TabBarController
{ {
self.selectedIndex = Tab.settings.rawValue self.selectedIndex = Tab.settings.rawValue
} }
@objc func exportFiles(_ notification: Notification)
{
self.selectedIndex = Tab.settings.rawValue
}
} }

View File

@@ -1,18 +0,0 @@
//
// 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
}

View File

@@ -53,12 +53,6 @@ public class Keychain
@KeychainItem(key: "appleIDPassword") @KeychainItem(key: "appleIDPassword")
public var appleIDPassword: String? public var appleIDPassword: String?
@KeychainItem(key: "appleIDAdsid")
public var appleIDAdsid: String?
@KeychainItem(key: "appleIDXcodeToken")
public var appleIDXcodeToken: String?
@KeychainItem(key: "signingCertificatePrivateKey") @KeychainItem(key: "signingCertificatePrivateKey")
public var signingCertificatePrivateKey: Data? public var signingCertificatePrivateKey: Data?
@@ -89,12 +83,6 @@ public class Keychain
@KeychainItem(key: "adiPb") @KeychainItem(key: "adiPb")
public var adiPb: String? 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() private init()
{ {
} }
@@ -103,13 +91,7 @@ public class Keychain
{ {
self.appleIDEmailAddress = nil self.appleIDEmailAddress = nil
self.appleIDPassword = nil self.appleIDPassword = nil
self.appleIDAdsid = nil
self.appleIDXcodeToken = nil
self.signingCertificatePrivateKey = nil self.signingCertificatePrivateKey = nil
self.signingCertificateSerialNumber = nil self.signingCertificateSerialNumber = nil
self.certificate = nil
self.session = nil
self.team = nil
} }
} }

View File

@@ -30,16 +30,12 @@ public extension UserDefaults
@NSManaged var preferredServerID: String? @NSManaged var preferredServerID: String?
@NSManaged var isBackgroundRefreshEnabled: Bool @NSManaged var isBackgroundRefreshEnabled: Bool
@NSManaged var enableEMPforWireguard: Bool
@NSManaged var isIdleTimeoutDisableEnabled: Bool @NSManaged var isIdleTimeoutDisableEnabled: Bool
@NSManaged var isAppLimitDisabled: Bool @NSManaged var isAppLimitDisabled: Bool
@NSManaged var isBetaUpdatesEnabled: Bool @NSManaged var isBetaUpdatesEnabled: Bool
@NSManaged var customizeAppId: Bool
@NSManaged var isExportResignedAppEnabled: Bool @NSManaged var isExportResignedAppEnabled: Bool
@NSManaged var isVerboseOperationsLoggingEnabled: Bool @NSManaged var isVerboseOperationsLoggingEnabled: Bool
@NSManaged var isMinimuxerConsoleLoggingEnabled: Bool @NSManaged var isMinimuxerConsoleLoggingEnabled: Bool
@NSManaged var isMinimuxerStatusCheckEnabled: Bool
@NSManaged var recreateDatabaseOnNextStart: Bool @NSManaged var recreateDatabaseOnNextStart: Bool
@NSManaged var isPairingReset: Bool @NSManaged var isPairingReset: Bool
@NSManaged var isDebugModeEnabled: Bool @NSManaged var isDebugModeEnabled: Bool
@@ -132,15 +128,12 @@ public extension UserDefaults
let defaults = [ let defaults = [
#keyPath(UserDefaults.isAppLimitDisabled): false, #keyPath(UserDefaults.isAppLimitDisabled): false,
#keyPath(UserDefaults.isBetaUpdatesEnabled): false, #keyPath(UserDefaults.isBetaUpdatesEnabled): false,
#keyPath(UserDefaults.customizeAppId): false,
#keyPath(UserDefaults.isExportResignedAppEnabled): false, #keyPath(UserDefaults.isExportResignedAppEnabled): false,
#keyPath(UserDefaults.isDebugModeEnabled): false, #keyPath(UserDefaults.isDebugModeEnabled): false,
#keyPath(UserDefaults.isVerboseOperationsLoggingEnabled): false, #keyPath(UserDefaults.isVerboseOperationsLoggingEnabled): false,
#keyPath(UserDefaults.isMinimuxerConsoleLoggingEnabled): false, // minimuxer logging is disabled by default for console loggin #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.recreateDatabaseOnNextStart): false,
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true, #keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
#keyPath(UserDefaults.enableEMPforWireguard): false,
#keyPath(UserDefaults.isIdleTimeoutDisableEnabled): true, #keyPath(UserDefaults.isIdleTimeoutDisableEnabled): true,
#keyPath(UserDefaults.isPairingReset): true, #keyPath(UserDefaults.isPairingReset): true,
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported, #keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,

View File

@@ -3,6 +3,6 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>_XCCurrentVersionName</key> <key>_XCCurrentVersionName</key>
<string>AltStore 17_1.xcdatamodel</string> <string>AltStore 17.xcdatamodel</string>
</dict> </dict>
</plist> </plist>

View File

@@ -1,316 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23605" systemVersion="24D70" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="v17.1">
<entity name="Account" representedClassName="Account" syncable="YES">
<attribute name="appleID" attributeType="String"/>
<attribute name="firstName" attributeType="String"/>
<attribute name="identifier" attributeType="String"/>
<attribute name="isActiveAccount" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="lastName" attributeType="String"/>
<relationship name="teams" toMany="YES" deletionRule="Cascade" destinationEntity="Team" inverseName="account" inverseEntity="Team"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="AppID" representedClassName="AppID" syncable="YES">
<attribute name="bundleIdentifier" attributeType="String"/>
<attribute name="expirationDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="features" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
<attribute name="identifier" attributeType="String"/>
<attribute name="name" attributeType="String"/>
<relationship name="team" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Team" inverseName="appIDs" inverseEntity="Team"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="AppPermission" representedClassName="AppPermission" syncable="YES">
<attribute name="appBundleID" optional="YES" attributeType="String"/>
<attribute name="permission" optional="YES" attributeType="String"/>
<attribute name="sourceID" optional="YES" attributeType="String"/>
<attribute name="type" attributeType="String"/>
<attribute name="usageDescription" attributeType="String"/>
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="permissions" inverseEntity="StoreApp"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="sourceID"/>
<constraint value="appBundleID"/>
<constraint value="type"/>
<constraint value="permission"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="AppScreenshot" representedClassName="AppScreenshot" syncable="YES">
<attribute name="appBundleID" optional="YES" attributeType="String"/>
<attribute name="deviceType" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="height" optional="YES" attributeType="Integer 16" usesScalarValueType="NO"/>
<attribute name="imageURL" attributeType="URI"/>
<attribute name="sourceID" optional="YES" attributeType="String"/>
<attribute name="width" optional="YES" attributeType="Integer 16" usesScalarValueType="NO"/>
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="screenshots" inverseEntity="StoreApp"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="sourceID"/>
<constraint value="appBundleID"/>
<constraint value="imageURL"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="AppVersion" representedClassName="AppVersion" syncable="YES">
<attribute name="appBundleID" optional="YES" attributeType="String"/>
<attribute name="buildVersion" optional="YES" attributeType="String"/>
<attribute name="channel" optional="YES" attributeType="String"/>
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="downloadURL" attributeType="URI"/>
<attribute name="localizedDescription" optional="YES" attributeType="String"/>
<attribute name="maxOSVersion" optional="YES" attributeType="String"/>
<attribute name="minOSVersion" optional="YES" attributeType="String"/>
<attribute name="sha256" optional="YES" attributeType="String"/>
<attribute name="size" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sourceID" optional="YES" attributeType="String"/>
<attribute name="version" attributeType="String"/>
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="versions" inverseEntity="StoreApp"/>
<relationship name="latestVersionApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="latestVersion" inverseEntity="StoreApp"/>
<relationship name="releaseTrack" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ReleaseTrack" inverseName="releases" inverseEntity="ReleaseTrack"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="sourceID"/>
<constraint value="appBundleID"/>
<constraint value="version"/>
<constraint value="buildVersion"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="InstalledApp" representedClassName="InstalledApp" syncable="YES">
<attribute name="buildVersion" optional="YES" attributeType="String"/>
<attribute name="bundleIdentifier" attributeType="String"/>
<attribute name="certificateSerialNumber" optional="YES" attributeType="String"/>
<attribute name="expirationDate" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="hasAlternateIcon" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="hasUpdate" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="installedDate" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="isActive" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="isRefreshing" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="name" attributeType="String"/>
<attribute name="needsResign" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<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"/>
<relationship name="storeApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="installedApp" inverseEntity="StoreApp"/>
<relationship name="team" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Team" inverseName="installedApps" inverseEntity="Team"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="bundleIdentifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="InstalledExtension" representedClassName="InstalledExtension" syncable="YES">
<attribute name="bundleIdentifier" attributeType="String"/>
<attribute name="expirationDate" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="installedDate" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="name" attributeType="String"/>
<attribute name="refreshedDate" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="resignedBundleIdentifier" attributeType="String"/>
<attribute name="version" attributeType="String"/>
<relationship name="parentApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="appExtensions" inverseEntity="InstalledApp"/>
</entity>
<entity name="LoggedError" representedClassName="LoggedError" syncable="YES">
<attribute name="appBundleID" attributeType="String"/>
<attribute name="appName" attributeType="String"/>
<attribute name="code" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="domain" attributeType="String"/>
<attribute name="operation" optional="YES" attributeType="String"/>
<attribute name="userInfo" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
<relationship name="installedApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="loggedErrors" inverseEntity="InstalledApp"/>
<relationship name="storeApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="loggedErrors" inverseEntity="StoreApp"/>
</entity>
<entity name="NewsItem" representedClassName="NewsItem" syncable="YES">
<attribute name="appID" optional="YES" attributeType="String"/>
<attribute name="caption" attributeType="String"/>
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="externalURL" optional="YES" attributeType="URI"/>
<attribute name="identifier" attributeType="String"/>
<attribute name="imageURL" optional="YES" attributeType="URI"/>
<attribute name="isSilent" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="sortIndex" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sourceIdentifier" optional="YES" attributeType="String"/>
<attribute name="tintColor" optional="YES" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
<attribute name="title" attributeType="String"/>
<relationship name="source" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="newsItems" inverseEntity="Source"/>
<relationship name="storeApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="newsItems" inverseEntity="StoreApp"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
<constraint value="sourceIdentifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="PatreonAccount" representedClassName="PatreonAccount" syncable="YES">
<attribute name="firstName" optional="YES" attributeType="String"/>
<attribute name="identifier" attributeType="String"/>
<attribute name="isPatron" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="name" attributeType="String"/>
<relationship name="pledges" toMany="YES" deletionRule="Cascade" destinationEntity="Pledge" inverseName="account" inverseEntity="Pledge"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="Patron" representedClassName="ManagedPatron" syncable="YES">
<attribute name="identifier" attributeType="String"/>
<attribute name="name" attributeType="String"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="Pledge" representedClassName="Pledge" syncable="YES">
<attribute name="amount" attributeType="Decimal" defaultValueString="0"/>
<attribute name="campaignURL" attributeType="URI"/>
<attribute name="identifier" attributeType="String"/>
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PatreonAccount" inverseName="pledges" inverseEntity="PatreonAccount"/>
<relationship name="rewards" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="PledgeReward" inverseName="pledge" inverseEntity="PledgeReward"/>
<relationship name="tiers" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="PledgeTier" inverseName="pledge" inverseEntity="PledgeTier"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="PledgeReward" representedClassName="PledgeReward" syncable="YES">
<attribute name="identifier" attributeType="String"/>
<attribute name="name" attributeType="String"/>
<relationship name="pledge" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Pledge" inverseName="rewards" inverseEntity="Pledge"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="PledgeTier" representedClassName="PledgeTier" syncable="YES">
<attribute name="amount" attributeType="Decimal" defaultValueString="0.0"/>
<attribute name="identifier" attributeType="String"/>
<attribute name="name" optional="YES" attributeType="String"/>
<relationship name="pledge" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Pledge" inverseName="tiers" inverseEntity="Pledge"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="RefreshAttempt" representedClassName="RefreshAttempt" syncable="YES">
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="errorDescription" optional="YES" attributeType="String"/>
<attribute name="identifier" attributeType="String"/>
<attribute name="isSuccess" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="ReleaseTrack" representedClassName="ReleaseTrack" syncable="YES">
<attribute name="appBundleID" optional="YES" attributeType="String"/>
<attribute name="sourceID" optional="YES" attributeType="String"/>
<attribute name="track" attributeType="String"/>
<relationship name="releases" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppVersion" inverseName="releaseTrack" inverseEntity="AppVersion"/>
<relationship name="storeApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="releaseTracks" inverseEntity="StoreApp"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="sourceID"/>
<constraint value="appBundleID"/>
<constraint value="track"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="Source" representedClassName="Source" syncable="YES">
<attribute name="error" optional="YES" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
<attribute name="featuredSortID" optional="YES" attributeType="String"/>
<attribute name="groupID" optional="YES" attributeType="String"/>
<attribute name="hasFeaturedApps" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="headerImageURL" optional="YES" attributeType="URI"/>
<attribute name="iconURL" optional="YES" attributeType="URI"/>
<attribute name="identifier" attributeType="String"/>
<attribute name="localizedDescription" optional="YES" attributeType="String"/>
<attribute name="name" attributeType="String"/>
<attribute name="patreonURL" optional="YES" attributeType="URI"/>
<attribute name="sourceURL" attributeType="URI"/>
<attribute name="subtitle" optional="YES" attributeType="String"/>
<attribute name="tintColor" optional="YES" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
<attribute name="version" attributeType="Integer 64" defaultValueString="1" usesScalarValueType="YES"/>
<attribute name="websiteURL" optional="YES" attributeType="URI"/>
<relationship name="apps" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="StoreApp" inverseName="source" inverseEntity="StoreApp"/>
<relationship name="featuredApps" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="StoreApp" inverseName="featuringSource" inverseEntity="StoreApp"/>
<relationship name="newsItems" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="NewsItem" inverseName="source" inverseEntity="NewsItem"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="StoreApp" representedClassName="StoreApp" syncable="YES">
<attribute name="bundleIdentifier" attributeType="String"/>
<attribute name="category" optional="YES" attributeType="String"/>
<attribute name="developerName" attributeType="String"/>
<attribute name="downloadURL" optional="YES" attributeType="URI"/>
<attribute name="featuredSortID" optional="YES" attributeType="String"/>
<attribute name="iconURL" attributeType="URI"/>
<attribute name="isHiddenWithoutPledge" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="isPledged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="isPledgeRequired" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="localizedDescription" attributeType="String"/>
<attribute name="marketplaceID" optional="YES" attributeType="String"/>
<attribute name="name" attributeType="String"/>
<attribute name="pledgeAmount" optional="YES" attributeType="Decimal"/>
<attribute name="pledgeCurrency" optional="YES" attributeType="String"/>
<attribute name="prefersCustomPledge" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="screenshotURLs" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
<attribute name="size" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sortIndex" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sourceIdentifier" optional="YES" attributeType="String"/>
<attribute name="subtitle" optional="YES" attributeType="String"/>
<attribute name="tintColor" optional="YES" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
<attribute name="version" optional="YES" attributeType="String"/>
<attribute name="versionDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="versionDescription" optional="YES" attributeType="String"/>
<relationship name="featuringSource" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="featuredApps" inverseEntity="Source"/>
<relationship name="installedApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="storeApp" inverseEntity="InstalledApp"/>
<relationship name="latestVersion" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="AppVersion" inverseName="latestVersionApp" inverseEntity="AppVersion"/>
<relationship name="loggedErrors" toMany="YES" deletionRule="Nullify" destinationEntity="LoggedError" inverseName="storeApp" inverseEntity="LoggedError"/>
<relationship name="newsItems" toMany="YES" deletionRule="Nullify" destinationEntity="NewsItem" inverseName="storeApp" inverseEntity="NewsItem"/>
<relationship name="permissions" toMany="YES" deletionRule="Cascade" destinationEntity="AppPermission" inverseName="app" inverseEntity="AppPermission"/>
<relationship name="releaseTracks" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="ReleaseTrack" inverseName="storeApp" inverseEntity="ReleaseTrack"/>
<relationship name="screenshots" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppScreenshot" inverseName="app" inverseEntity="AppScreenshot"/>
<relationship name="source" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="apps" inverseEntity="Source"/>
<relationship name="versions" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppVersion" inverseName="app" inverseEntity="AppVersion"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="sourceIdentifier"/>
<constraint value="bundleIdentifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="Team" representedClassName="Team" syncable="YES">
<attribute name="identifier" attributeType="String"/>
<attribute name="isActiveTeam" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="name" attributeType="String"/>
<attribute name="type" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Account" inverseName="teams" inverseEntity="Account"/>
<relationship name="appIDs" toMany="YES" deletionRule="Cascade" destinationEntity="AppID" inverseName="team" inverseEntity="AppID"/>
<relationship name="installedApps" toMany="YES" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="team" inverseEntity="InstalledApp"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="identifier"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
</model>

View File

@@ -33,7 +33,7 @@ public class AppPermission: BaseEntity
default: return UnknownAppPermission(rawValue: self._permission) default: return UnknownAppPermission(rawValue: self._permission)
} }
} }
@NSManaged @objc(permission) private(set) public var _permission: String @NSManaged @objc(permission) private var _permission: String
// Set by StoreApp. // Set by StoreApp.
@NSManaged public var appBundleID: String @NSManaged public var appBundleID: String

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