mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-13 16:53:29 +01:00
Compare commits
63 Commits
0.5.6
...
fabianthde
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2939919ddb | ||
|
|
38a1c7eef6 | ||
|
|
f6252c3a8b | ||
|
|
653d80b88e | ||
|
|
89609ad35c | ||
|
|
2211013e57 | ||
|
|
f206ee1406 | ||
|
|
00dc9b36af | ||
|
|
24146cef90 | ||
|
|
c46a50ec58 | ||
|
|
de7e909c01 | ||
|
|
fbc754d8b7 | ||
|
|
767d878051 | ||
|
|
132b140af2 | ||
|
|
df7d8871ff | ||
|
|
ca2398e4c7 | ||
|
|
b8f02d2152 | ||
|
|
e85876cd24 | ||
|
|
3f06a53058 | ||
|
|
4ee053a4f9 | ||
|
|
e5369524ce | ||
|
|
77465cebd0 | ||
|
|
f90bf3bfcf | ||
|
|
0000610b9d | ||
|
|
c7e095583d | ||
|
|
a725f3e9cc | ||
|
|
b5dea18073 | ||
|
|
b9b309e603 | ||
|
|
15f1be0aa8 | ||
|
|
ffd80ce0b4 | ||
|
|
350891ee2a | ||
|
|
5dec1cd561 | ||
|
|
c4d235d742 | ||
|
|
cdc6675dd5 | ||
|
|
85635bb26e | ||
|
|
3be0a4a89c | ||
|
|
47e47fb3cf | ||
|
|
48903034b6 | ||
|
|
6952218ee7 | ||
|
|
80146c1e03 | ||
|
|
642ae996c9 | ||
|
|
8040636aa5 | ||
|
|
731fcfaca7 | ||
|
|
708fb3fccd | ||
|
|
9f429fb068 | ||
|
|
29fc693f4d | ||
|
|
6f373ad305 | ||
|
|
c069d779d9 | ||
|
|
cd88970a22 | ||
|
|
6b6708e43c | ||
|
|
9206eeb9e3 | ||
|
|
080bbb3c51 | ||
|
|
ea2c862900 | ||
|
|
4fe72ea113 | ||
|
|
c486a62b50 | ||
|
|
3ce4451da4 | ||
|
|
294ba12391 | ||
|
|
4a3343fe61 | ||
|
|
d1e6ddd435 | ||
|
|
3e0379dc70 | ||
|
|
d99674f8bd | ||
|
|
ca7acc17da | ||
|
|
16a8bce102 |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1 +1 @@
|
||||
* @JoeMatt @lonkelle @nythepegasus @Spidy123222 @SternXD
|
||||
* @JoeMatt @lonkelle
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
5
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -2,14 +2,15 @@ name: Bug Report
|
||||
description: Report a bug
|
||||
title: "[BUG] "
|
||||
labels: ["bug"]
|
||||
assignees: []
|
||||
assignees:
|
||||
- naturecodevoid
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report! Before you continue filling out the report, please **[search in GitHub Issues](https://github.com/SideStore/SideStore/issues?q=is%3Aissue+is%3Aopen) for the bug you are experiencing** in case it has already been reported.
|
||||
|
||||
**Please use [Discord](https://discord.gg/sidestore-949183273383395328) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
|
||||
**Please use [Discord](https://discord.gg/RgpFBX3Q3k) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -3,7 +3,7 @@ blank_issues_enabled: false
|
||||
|
||||
contact_links:
|
||||
- name: Discord
|
||||
url: https://discord.gg/sidestore-949183273383395328
|
||||
url: https://discord.gg/RgpFBX3Q3k
|
||||
about: If you need support, please go here first instead of making an issue!
|
||||
- name: GitHub Discussions
|
||||
url: https://github.com/SideStore/SideStore/discussions
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
5
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -2,14 +2,15 @@ name: Feature Request
|
||||
description: Suggest a feature
|
||||
title: "[FEATURE REQUEST] "
|
||||
labels: ["enhancement"]
|
||||
assignees: []
|
||||
assignees:
|
||||
- naturecodevoid
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this feature request! Before you continue filling out the form, please **[search in GitHub Issues](https://github.com/SideStore/SideStore/issues?q=is%3Aissue+is%3Aopen) for the feature you are suggestion** in case it has already been suggested.
|
||||
|
||||
**Please use [Discord](https://discord.gg/sidestore-949183273383395328) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
|
||||
**Please use [Discord](https://discord.gg/RgpFBX3Q3k) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -9,7 +9,7 @@
|
||||
url = https://github.com/libimobiledevice/libusbmuxd.git
|
||||
[submodule "Dependencies/libplist"]
|
||||
path = Dependencies/libplist
|
||||
url = https://github.com/SideStore/libplist.git
|
||||
url = https://github.com/libimobiledevice/libplist.git
|
||||
[submodule "Dependencies/MarkdownAttributedString"]
|
||||
path = Dependencies/MarkdownAttributedString
|
||||
url = https://github.com/chockenberry/MarkdownAttributedString.git
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "175",
|
||||
"green" : "4",
|
||||
"red" : "115"
|
||||
"blue" : "0.518",
|
||||
"green" : "0.502",
|
||||
"red" : "0.004"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
@@ -23,9 +23,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "150",
|
||||
"green" : "3",
|
||||
"red" : "99"
|
||||
"blue" : "0.404",
|
||||
"green" : "0.322",
|
||||
"red" : "0.008"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
||||
"location" : "https://github.com/SideStore/AltSign",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "cc6189f0f7cd8e5bd24943af9322e0ff9420e9f4"
|
||||
"revision" : "7e0e7edcf8fbc44ac1e35da3e9030a297aa18b84"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -14,17 +14,26 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/microsoft/appcenter-sdk-apple.git",
|
||||
"state" : {
|
||||
"revision" : "b2dc99cfedead0bad4e6573d86c5228c89cff332",
|
||||
"version" : "4.4.3"
|
||||
"revision" : "8354a50fe01a7e54e196d3b5493b5ab53dd5866a",
|
||||
"version" : "4.4.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "imobiledevice.swift",
|
||||
"identity" : "asyncimage",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SideStore/iMobileDevice.swift",
|
||||
"location" : "https://github.com/fabianthdev/AsyncImage",
|
||||
"state" : {
|
||||
"revision" : "74e481106dd155c0cd21bca6795fd9fe5f751654",
|
||||
"version" : "1.0.5"
|
||||
"branch" : "main",
|
||||
"revision" : "018a4fffea025066d795ebb025c2769183f3fffb"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "expandabletext",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/fabianthdev/ExpandableText",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "a375f5b8c73f0af69aa7add890378fdf404a29bc"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -45,6 +54,15 @@
|
||||
"version" : "4.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "localconsole",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/duraidabdul/LocalConsole.git",
|
||||
"state" : {
|
||||
"revision" : "2c5d5e018acd4963fe6dfe858f6d6fecef7cbf2f",
|
||||
"version" : "1.12.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "nuke",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -59,8 +77,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/krzyzanowskim/OpenSSL",
|
||||
"state" : {
|
||||
"revision" : "0faf71a188bcfdf0245cab42886b9b240ca71c52",
|
||||
"version" : "1.1.2200"
|
||||
"revision" : "033fcb41dac96b1b6effa945ca1f9ade002370b2",
|
||||
"version" : "1.1.1501"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -68,8 +86,17 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/microsoft/PLCrashReporter.git",
|
||||
"state" : {
|
||||
"revision" : "81cdec2b3827feb03286cb297f4c501a8eb98df1",
|
||||
"version" : "1.10.2"
|
||||
"revision" : "6b27393cad517c067dceea85fadf050e70c4ceaa",
|
||||
"version" : "1.10.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "reachability.swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/ashleymills/Reachability.swift",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "a81b7367f2c46875f29577e03a60c39cdfad0c8d"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -77,8 +104,17 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SwiftPackageIndex/SemanticVersion.git",
|
||||
"state" : {
|
||||
"revision" : "a70840d5fca686ae3bd2fcf8aecc5ded0bd4f125",
|
||||
"version" : "0.3.6"
|
||||
"revision" : "fc670910dc0903cc269b3d0b776cda5703979c4e",
|
||||
"version" : "0.3.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sfsafesymbols",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SFSafeSymbols/SFSafeSymbols",
|
||||
"state" : {
|
||||
"revision" : "50bc33264e6c0972f905b61af656201cf6091de8",
|
||||
"version" : "4.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -86,8 +122,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/sparkle-project/Sparkle.git",
|
||||
"state" : {
|
||||
"revision" : "f0ceaf5cc9f3f23daa0ccb6dcebd79fc96ccc7d9",
|
||||
"version" : "2.5.0"
|
||||
"revision" : "286edd1fa22505a9e54d170e9fd07d775ea233f2",
|
||||
"version" : "2.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -95,8 +131,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/daltoniam/Starscream.git",
|
||||
"state" : {
|
||||
"revision" : "ac6c0fc9da221873e01bd1a0d4818498a71eef33",
|
||||
"version" : "4.0.6"
|
||||
"revision" : "df8d82047f6654d8e4b655d1b1525c64e1059d21",
|
||||
"version" : "4.0.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -217,8 +217,8 @@ final class AppViewController: UIViewController
|
||||
|
||||
self._shouldResetLayout = false
|
||||
}
|
||||
|
||||
let statusBarHeight = self.view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
|
||||
|
||||
let statusBarHeight = UIApplication.shared.statusBarFrame.height
|
||||
let cornerRadius = self.contentViewControllerShadowView.layer.cornerRadius
|
||||
|
||||
let inset = 12 as CGFloat
|
||||
@@ -323,7 +323,7 @@ final class AppViewController: UIViewController
|
||||
|
||||
self.backButtonContainerView.layer.cornerRadius = self.backButtonContainerView.bounds.midY
|
||||
|
||||
self.scrollView.verticalScrollIndicatorInsets.top = statusBarHeight
|
||||
self.scrollView.scrollIndicatorInsets.top = statusBarHeight
|
||||
|
||||
// Adjust content offset + size.
|
||||
let contentOffset = self.scrollView.contentOffset
|
||||
|
||||
@@ -90,21 +90,14 @@ private extension AppIDsViewController
|
||||
cell.bannerView.button.isUserInteractionEnabled = false
|
||||
|
||||
cell.bannerView.buttonLabel.isHidden = false
|
||||
|
||||
|
||||
let currentDate = Date()
|
||||
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.unitsStyle = .full
|
||||
formatter.includesApproximationPhrase = false
|
||||
formatter.includesTimeRemainingPhrase = false
|
||||
formatter.allowedUnits = [.minute, .hour, .day]
|
||||
formatter.maximumUnitCount = 1
|
||||
let numberOfDays = expirationDate.numberOfCalendarDays(since: currentDate)
|
||||
let numberOfDaysText = (numberOfDays == 1) ? NSLocalizedString("1 day", comment: "") : String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
||||
|
||||
cell.bannerView.button.setTitle((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")).uppercased(), for: .normal)
|
||||
|
||||
// formatter.includesTimeRemainingPhrase = true
|
||||
|
||||
// attributedAccessibilityLabel.mutableString.append((formatter.string(from: currentDate, to: expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " ")
|
||||
attributedAccessibilityLabel.mutableString.append(String(format: NSLocalizedString("Expires in %@.", comment: ""), numberOfDaysText) + " ")
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
18
AltStore/App/SideStoreUIApp.swift
Normal file
18
AltStore/App/SideStoreUIApp.swift
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// SideStoreUIApp.swift
|
||||
// SideStoreUI
|
||||
//
|
||||
// Created by Fabian Thies on 18.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct SideStoreUIApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
RootView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,9 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
|
||||
{
|
||||
// Copy STDOUT and STDERR to the logging console
|
||||
_ = OutputCapturer.shared
|
||||
|
||||
// Register default settings before doing anything else.
|
||||
UserDefaults.registerDefaults()
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="wKh-xq-NuP">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21223" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="wKh-xq-NuP">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21204"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
@@ -356,8 +356,8 @@
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="ewH-gi-pyW">
|
||||
<rect key="frame" x="0.0" y="30.5" width="335" height="17"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Version 0.5.6" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7E0-TV-G4l">
|
||||
<rect key="frame" x="0.0" y="0.0" width="84" height="17"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Version 4.4.2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7E0-TV-G4l">
|
||||
<rect key="frame" x="0.0" y="0.0" width="84.5" height="17"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -596,7 +596,7 @@ World</string>
|
||||
<tabBarItem key="tabBarItem" title="Browse" image="Browse" id="Uwh-Bg-Ymq"/>
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="dIv-qd-9L5" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="tintColor" name="Primary"/>
|
||||
</navigationBar>
|
||||
@@ -626,7 +626,7 @@ World</string>
|
||||
</tabBarItem>
|
||||
<toolbarItems/>
|
||||
<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="0.0" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -883,7 +883,7 @@ World</string>
|
||||
<navigationItem key="navigationItem" title="App IDs" id="3Co-uv-Fhb">
|
||||
<barButtonItem key="leftBarButtonItem" style="plain" id="Aqs-QK-Ups">
|
||||
<view key="customView" contentMode="scaleToFill" id="p0q-Fg-3Ba">
|
||||
<rect key="frame" x="16" y="7" width="83" height="42"/>
|
||||
<rect key="frame" x="16" y="1" width="83" height="42"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
</view>
|
||||
</barButtonItem>
|
||||
@@ -909,7 +909,7 @@ World</string>
|
||||
<tabBarItem key="tabBarItem" title="News" image="News" id="fVN-ed-uO1"/>
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="525-jF-uDK" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
|
||||
</navigationBar>
|
||||
@@ -928,7 +928,7 @@ World</string>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="IXk-qg-mFJ" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<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="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -1070,7 +1070,7 @@ World</string>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="Qo4-72-Hmr" sceneMemberID="viewController">
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="mcx-oR-qPe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="108"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -1095,13 +1095,13 @@ World</string>
|
||||
<image name="News" width="19" height="20"/>
|
||||
<image name="Settings" width="20" height="20"/>
|
||||
<namedColor name="Background">
|
||||
<color red="0.45098039215686275" green="0.015686274509803921" blue="0.68627450980392157" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.6431" green="0.0196" blue="0.9804" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<namedColor name="BlurTint">
|
||||
<color red="1" green="1" blue="1" alpha="0.30000001192092896" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="1" green="1" blue="1" alpha="0.3" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<namedColor name="Primary">
|
||||
<color red="0.64313725490196083" green="0.019607843137254902" blue="0.98039215686274506" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.6431" green="0.0196" blue="0.9804" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
import minimuxer
|
||||
import AltStoreCore
|
||||
import Roxas
|
||||
|
||||
@@ -265,13 +264,7 @@ private extension BrowseViewController
|
||||
previousProgress?.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
_ = AppManager.shared.install(app, presentingViewController: self) { (result) in
|
||||
DispatchQueue.main.async {
|
||||
switch result
|
||||
|
||||
@@ -22,7 +22,7 @@ final class CollapsingTextView: UITextView
|
||||
}
|
||||
}
|
||||
|
||||
var lineSpacing: Double = 2 {
|
||||
var lineSpacing: CGFloat = 2 {
|
||||
didSet {
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
@@ -34,19 +34,7 @@ final class CollapsingTextView: UITextView
|
||||
{
|
||||
super.awakeFromNib()
|
||||
|
||||
self.initialize()
|
||||
}
|
||||
|
||||
private func initialize()
|
||||
{
|
||||
if #available(iOS 16, *)
|
||||
{
|
||||
self.updateText()
|
||||
}
|
||||
else
|
||||
{
|
||||
self.layoutManager.delegate = self
|
||||
}
|
||||
self.layoutManager.delegate = self
|
||||
|
||||
self.textContainerInset = .zero
|
||||
self.textContainer.lineFragmentPadding = 0
|
||||
@@ -120,25 +108,6 @@ private extension CollapsingTextView
|
||||
{
|
||||
self.isCollapsed.toggle()
|
||||
}
|
||||
|
||||
@available(iOS 16, *)
|
||||
func updateText()
|
||||
{
|
||||
do
|
||||
{
|
||||
let style = NSMutableParagraphStyle()
|
||||
style.lineSpacing = self.lineSpacing
|
||||
|
||||
var attributedText = try AttributedString(self.attributedText, including: \.uiKit)
|
||||
attributedText[AttributeScopes.UIKitAttributes.ParagraphStyleAttribute.self] = style
|
||||
|
||||
self.attributedText = NSAttributedString(attributedText)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("[ALTLog] Failed to update CollapsingTextView line spacing:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CollapsingTextView: NSLayoutManagerDelegate
|
||||
|
||||
@@ -8,12 +8,6 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
extension PillButton
|
||||
{
|
||||
static let minimumSize = CGSize(width: 77, height: 31)
|
||||
static let contentInsets = NSDirectionalEdgeInsets(top: 7, leading: 13, bottom: 7, trailing: 13)
|
||||
}
|
||||
|
||||
final class PillButton: UIButton
|
||||
{
|
||||
override var accessibilityValue: String? {
|
||||
@@ -76,7 +70,9 @@ final class PillButton: UIButton
|
||||
}()
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
let size = self.sizeThatFits(CGSize(width: Double.infinity, height: .infinity))
|
||||
var size = super.intrinsicContentSize
|
||||
size.width += 26
|
||||
size.height += 3
|
||||
return size
|
||||
}
|
||||
|
||||
@@ -92,8 +88,6 @@ final class PillButton: UIButton
|
||||
self.layer.masksToBounds = true
|
||||
self.accessibilityTraits.formUnion([.updatesFrequently, .button])
|
||||
|
||||
self.contentEdgeInsets = UIEdgeInsets(top: Self.contentInsets.top, left: Self.contentInsets.leading, bottom: Self.contentInsets.bottom, right: Self.contentInsets.trailing)
|
||||
|
||||
self.activityIndicatorView.style = .medium
|
||||
self.activityIndicatorView.isUserInteractionEnabled = false
|
||||
|
||||
@@ -125,15 +119,6 @@ final class PillButton: UIButton
|
||||
|
||||
self.update()
|
||||
}
|
||||
|
||||
override func sizeThatFits(_ size: CGSize) -> CGSize
|
||||
{
|
||||
var size = super.sizeThatFits(size)
|
||||
size.width = max(size.width, PillButton.minimumSize.width)
|
||||
size.height = max(size.height, PillButton.minimumSize.height)
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
private extension PillButton
|
||||
|
||||
19
AltStore/Extensions/Source+Trusted.swift
Normal file
19
AltStore/Extensions/Source+Trusted.swift
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Source+Trusted.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 04.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
extension Source {
|
||||
var isOfficial: Bool {
|
||||
self.identifier == Source.altStoreIdentifier
|
||||
}
|
||||
|
||||
var isTrusted: Bool {
|
||||
UserDefaults.shared.trustedSourceIDs?.contains(self.identifier) ?? false
|
||||
}
|
||||
}
|
||||
17
AltStore/Extensions/StoreApp+Filterable.swift
Normal file
17
AltStore/Extensions/StoreApp+Filterable.swift
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// StoreApp+Searchable.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 01.12.22.
|
||||
// Copyright © 2022 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
extension StoreApp: Filterable {
|
||||
func matches(_ searchText: String) -> Bool {
|
||||
searchText.isEmpty ||
|
||||
self.name.lowercased().contains(searchText.lowercased()) ||
|
||||
self.developerName.lowercased().contains(searchText.lowercased())
|
||||
}
|
||||
}
|
||||
19
AltStore/Extensions/StoreApp+Trusted.swift
Normal file
19
AltStore/Extensions/StoreApp+Trusted.swift
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// StoreApp+Trusted.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 04.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
extension StoreApp {
|
||||
var isFromOfficialSource: Bool {
|
||||
self.source?.isOfficial ?? false
|
||||
}
|
||||
|
||||
var isFromTrustedSource: Bool {
|
||||
self.source?.isTrusted ?? false
|
||||
}
|
||||
}
|
||||
201
AltStore/Generated/Assets.swift
Normal file
201
AltStore/Generated/Assets.swift
Normal file
@@ -0,0 +1,201 @@
|
||||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
#elseif os(iOS)
|
||||
import UIKit
|
||||
#elseif os(tvOS) || os(watchOS)
|
||||
import UIKit
|
||||
#endif
|
||||
#if canImport(SwiftUI)
|
||||
import SwiftUI
|
||||
#endif
|
||||
|
||||
// Deprecated typealiases
|
||||
@available(*, deprecated, renamed: "ColorAsset.Color", message: "This typealias will be removed in SwiftGen 7.0")
|
||||
internal typealias AssetColorTypeAlias = ColorAsset.Color
|
||||
@available(*, deprecated, renamed: "ImageAsset.Image", message: "This typealias will be removed in SwiftGen 7.0")
|
||||
internal typealias AssetImageTypeAlias = ImageAsset.Image
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length implicit_return
|
||||
|
||||
// MARK: - Asset Catalogs
|
||||
|
||||
// swiftlint:disable identifier_name line_length nesting type_body_length type_name
|
||||
internal enum Asset {
|
||||
internal static let back = ImageAsset(name: "Back")
|
||||
internal static let betaBadge = ImageAsset(name: "BetaBadge")
|
||||
internal static let accentColor = ColorAsset(name: "AccentColor")
|
||||
internal static let background = ColorAsset(name: "Background")
|
||||
internal static let blurTint = ColorAsset(name: "BlurTint")
|
||||
internal static let settingsBackground = ColorAsset(name: "SettingsBackground")
|
||||
internal static let settingsHighlighted = ColorAsset(name: "SettingsHighlighted")
|
||||
internal static let next = ImageAsset(name: "Next")
|
||||
internal static let riley = ImageAsset(name: "Riley")
|
||||
internal static let shane = ImageAsset(name: "Shane")
|
||||
internal static let browse = ImageAsset(name: "Browse")
|
||||
internal static let myApps = ImageAsset(name: "MyApps")
|
||||
internal static let news = ImageAsset(name: "News")
|
||||
internal static let settings = ImageAsset(name: "Settings")
|
||||
}
|
||||
// swiftlint:enable identifier_name line_length nesting type_body_length type_name
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
internal final class ColorAsset {
|
||||
internal fileprivate(set) var name: String
|
||||
|
||||
#if os(macOS)
|
||||
internal typealias Color = NSColor
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
internal typealias Color = UIColor
|
||||
#endif
|
||||
|
||||
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
|
||||
internal private(set) lazy var color: Color = {
|
||||
guard let color = Color(asset: self) else {
|
||||
fatalError("Unable to load color asset named \(name).")
|
||||
}
|
||||
return color
|
||||
}()
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
@available(iOS 11.0, tvOS 11.0, *)
|
||||
internal func color(compatibleWith traitCollection: UITraitCollection) -> Color {
|
||||
let bundle = BundleToken.bundle
|
||||
guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else {
|
||||
fatalError("Unable to load color asset named \(name).")
|
||||
}
|
||||
return color
|
||||
}
|
||||
#endif
|
||||
|
||||
#if canImport(SwiftUI)
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
internal private(set) lazy var swiftUIColor: SwiftUI.Color = {
|
||||
SwiftUI.Color(asset: self)
|
||||
}()
|
||||
#endif
|
||||
|
||||
fileprivate init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
internal extension ColorAsset.Color {
|
||||
@available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *)
|
||||
convenience init?(asset: ColorAsset) {
|
||||
let bundle = BundleToken.bundle
|
||||
#if os(iOS) || os(tvOS)
|
||||
self.init(named: asset.name, in: bundle, compatibleWith: nil)
|
||||
#elseif os(macOS)
|
||||
self.init(named: NSColor.Name(asset.name), bundle: bundle)
|
||||
#elseif os(watchOS)
|
||||
self.init(named: asset.name)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(SwiftUI)
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
internal extension SwiftUI.Color {
|
||||
init(asset: ColorAsset) {
|
||||
let bundle = BundleToken.bundle
|
||||
self.init(asset.name, bundle: bundle)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
internal struct ImageAsset {
|
||||
internal fileprivate(set) var name: String
|
||||
|
||||
#if os(macOS)
|
||||
internal typealias Image = NSImage
|
||||
#elseif os(iOS) || os(tvOS) || os(watchOS)
|
||||
internal typealias Image = UIImage
|
||||
#endif
|
||||
|
||||
@available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *)
|
||||
internal var image: Image {
|
||||
let bundle = BundleToken.bundle
|
||||
#if os(iOS) || os(tvOS)
|
||||
let image = Image(named: name, in: bundle, compatibleWith: nil)
|
||||
#elseif os(macOS)
|
||||
let name = NSImage.Name(self.name)
|
||||
let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name)
|
||||
#elseif os(watchOS)
|
||||
let image = Image(named: name)
|
||||
#endif
|
||||
guard let result = image else {
|
||||
fatalError("Unable to load image asset named \(name).")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
@available(iOS 8.0, tvOS 9.0, *)
|
||||
internal func image(compatibleWith traitCollection: UITraitCollection) -> Image {
|
||||
let bundle = BundleToken.bundle
|
||||
guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else {
|
||||
fatalError("Unable to load image asset named \(name).")
|
||||
}
|
||||
return result
|
||||
}
|
||||
#endif
|
||||
|
||||
#if canImport(SwiftUI)
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
internal var swiftUIImage: SwiftUI.Image {
|
||||
SwiftUI.Image(asset: self)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
internal extension ImageAsset.Image {
|
||||
@available(iOS 8.0, tvOS 9.0, watchOS 2.0, *)
|
||||
@available(macOS, deprecated,
|
||||
message: "This initializer is unsafe on macOS, please use the ImageAsset.image property")
|
||||
convenience init?(asset: ImageAsset) {
|
||||
#if os(iOS) || os(tvOS)
|
||||
let bundle = BundleToken.bundle
|
||||
self.init(named: asset.name, in: bundle, compatibleWith: nil)
|
||||
#elseif os(macOS)
|
||||
self.init(named: NSImage.Name(asset.name))
|
||||
#elseif os(watchOS)
|
||||
self.init(named: asset.name)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(SwiftUI)
|
||||
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
|
||||
internal extension SwiftUI.Image {
|
||||
init(asset: ImageAsset) {
|
||||
let bundle = BundleToken.bundle
|
||||
self.init(asset.name, bundle: bundle)
|
||||
}
|
||||
|
||||
init(asset: ImageAsset, label: Text) {
|
||||
let bundle = BundleToken.bundle
|
||||
self.init(asset.name, bundle: bundle, label: label)
|
||||
}
|
||||
|
||||
init(decorative asset: ImageAsset) {
|
||||
let bundle = BundleToken.bundle
|
||||
self.init(decorative: asset.name, bundle: bundle)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
351
AltStore/Generated/Localizations.swift
Normal file
351
AltStore/Generated/Localizations.swift
Normal file
@@ -0,0 +1,351 @@
|
||||
// swiftlint:disable all
|
||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable superfluous_disable_command file_length implicit_return prefer_self_in_static_references
|
||||
|
||||
// MARK: - Strings
|
||||
|
||||
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
|
||||
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
internal enum L10n {
|
||||
internal enum Action {
|
||||
/// Close
|
||||
internal static let close = L10n.tr("Localizable", "Action.close", fallback: "Close")
|
||||
/// General Actions
|
||||
internal static let done = L10n.tr("Localizable", "Action.done", fallback: "Done")
|
||||
}
|
||||
internal enum AddSourceView {
|
||||
/// Continue
|
||||
internal static let `continue` = L10n.tr("Localizable", "AddSourceView.continue", fallback: "Continue")
|
||||
/// AddSourceView
|
||||
internal static let sourceURL = L10n.tr("Localizable", "AddSourceView.sourceURL", fallback: "Source URL")
|
||||
/// Please enter the source url here. Then, tap continue to validate and add the source in the next step.
|
||||
internal static let sourceWarning = L10n.tr("Localizable", "AddSourceView.sourceWarning", fallback: "Please enter the source url here. Then, tap continue to validate and add the source in the next step.")
|
||||
/// Be careful with unvalidated third-party sources! Make sure to only add sources that you trust.
|
||||
internal static let sourceWarningContinued = L10n.tr("Localizable", "AddSourceView.sourceWarningContinued", fallback: "Be careful with unvalidated third-party sources! Make sure to only add sources that you trust.")
|
||||
/// Add Source
|
||||
internal static let title = L10n.tr("Localizable", "AddSourceView.title", fallback: "Add Source")
|
||||
}
|
||||
internal enum AppAction {
|
||||
/// Activate
|
||||
internal static let activate = L10n.tr("Localizable", "AppAction.activate", fallback: "Activate")
|
||||
/// Backup
|
||||
internal static let backup = L10n.tr("Localizable", "AppAction.backup", fallback: "Backup")
|
||||
/// Customize icon
|
||||
internal static let chooseCustomIcon = L10n.tr("Localizable", "AppAction.chooseCustomIcon", fallback: "Customize icon")
|
||||
/// Deactivate
|
||||
internal static let deactivate = L10n.tr("Localizable", "AppAction.deactivate", fallback: "Deactivate")
|
||||
/// Activate JIT
|
||||
internal static let enableJIT = L10n.tr("Localizable", "AppAction.enableJIT", fallback: "Activate JIT")
|
||||
/// Export backup
|
||||
internal static let exportBackup = L10n.tr("Localizable", "AppAction.exportBackup", fallback: "Export backup")
|
||||
/// AppAction
|
||||
internal static let install = L10n.tr("Localizable", "AppAction.install", fallback: "Install")
|
||||
/// Open
|
||||
internal static let `open` = L10n.tr("Localizable", "AppAction.open", fallback: "Open")
|
||||
/// Refresh
|
||||
internal static let refresh = L10n.tr("Localizable", "AppAction.refresh", fallback: "Refresh")
|
||||
/// Remove
|
||||
internal static let remove = L10n.tr("Localizable", "AppAction.remove", fallback: "Remove")
|
||||
/// Reset icon
|
||||
internal static let resetIcon = L10n.tr("Localizable", "AppAction.resetIcon", fallback: "Reset icon")
|
||||
/// Restore backup
|
||||
internal static let restoreBackup = L10n.tr("Localizable", "AppAction.restoreBackup", fallback: "Restore backup")
|
||||
}
|
||||
internal enum AppDetailView {
|
||||
/// Information
|
||||
internal static let information = L10n.tr("Localizable", "AppDetailView.information", fallback: "Information")
|
||||
/// More...
|
||||
internal static let more = L10n.tr("Localizable", "AppDetailView.more", fallback: "More...")
|
||||
/// The app requires no permissions.
|
||||
internal static let noPermissions = L10n.tr("Localizable", "AppDetailView.noPermissions", fallback: "The app requires no permissions.")
|
||||
/// No screenshots available for this app.
|
||||
internal static let noScreenshots = L10n.tr("Localizable", "AppDetailView.noScreenshots", fallback: "No screenshots available for this app.")
|
||||
/// No version information
|
||||
internal static let noVersionInformation = L10n.tr("Localizable", "AppDetailView.noVersionInformation", fallback: "No version information")
|
||||
/// Permissions
|
||||
internal static let permissions = L10n.tr("Localizable", "AppDetailView.permissions", fallback: "Permissions")
|
||||
/// Ratings & Reviews
|
||||
internal static let reviews = L10n.tr("Localizable", "AppDetailView.reviews", fallback: "Ratings & Reviews")
|
||||
/// Version %@
|
||||
internal static func version(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "AppDetailView.version", String(describing: p1), fallback: "Version %@")
|
||||
}
|
||||
/// What's New
|
||||
internal static let whatsNew = L10n.tr("Localizable", "AppDetailView.whatsNew", fallback: "What's New")
|
||||
internal enum Badge {
|
||||
/// AppDetailView
|
||||
internal static let official = L10n.tr("Localizable", "AppDetailView.Badge.official", fallback: "Official App")
|
||||
/// From Trusted Source
|
||||
internal static let trusted = L10n.tr("Localizable", "AppDetailView.Badge.trusted", fallback: "From Trusted Source")
|
||||
}
|
||||
internal enum Information {
|
||||
/// Compatibility
|
||||
internal static let compatibility = L10n.tr("Localizable", "AppDetailView.Information.compatibility", fallback: "Compatibility")
|
||||
/// Requires iOS %@ or higher
|
||||
internal static func compatibilityAtLeast(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "AppDetailView.Information.compatibilityAtLeast", String(describing: p1), fallback: "Requires iOS %@ or higher")
|
||||
}
|
||||
/// Unknown
|
||||
internal static let compatibilityCompatible = L10n.tr("Localizable", "AppDetailView.Information.compatibilityCompatible", fallback: "Unknown")
|
||||
/// Requires iOS %@ or lower
|
||||
internal static func compatibilityOrLower(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "AppDetailView.Information.compatibilityOrLower", String(describing: p1), fallback: "Requires iOS %@ or lower")
|
||||
}
|
||||
/// Unknown
|
||||
internal static let compatibilityUnknown = L10n.tr("Localizable", "AppDetailView.Information.compatibilityUnknown", fallback: "Unknown")
|
||||
/// Developer
|
||||
internal static let developer = L10n.tr("Localizable", "AppDetailView.Information.developer", fallback: "Developer")
|
||||
/// Latest Version
|
||||
internal static let latestVersion = L10n.tr("Localizable", "AppDetailView.Information.latestVersion", fallback: "Latest Version")
|
||||
/// Size
|
||||
internal static let size = L10n.tr("Localizable", "AppDetailView.Information.size", fallback: "Size")
|
||||
/// Source
|
||||
internal static let source = L10n.tr("Localizable", "AppDetailView.Information.source", fallback: "Source")
|
||||
}
|
||||
internal enum Reviews {
|
||||
/// out of %d
|
||||
internal static func outOf(_ p1: Int) -> String {
|
||||
return L10n.tr("Localizable", "AppDetailView.Reviews.outOf", p1, fallback: "out of %d")
|
||||
}
|
||||
/// %d Ratings
|
||||
internal static func ratings(_ p1: Int) -> String {
|
||||
return L10n.tr("Localizable", "AppDetailView.Reviews.ratings", p1, fallback: "%d Ratings")
|
||||
}
|
||||
/// See All
|
||||
internal static let seeAll = L10n.tr("Localizable", "AppDetailView.Reviews.seeAll", fallback: "See All")
|
||||
}
|
||||
internal enum WhatsNew {
|
||||
/// Show project on GitHub
|
||||
internal static let showOnGithub = L10n.tr("Localizable", "AppDetailView.WhatsNew.showOnGithub", fallback: "Show project on GitHub")
|
||||
/// Version History
|
||||
internal static let versionHistory = L10n.tr("Localizable", "AppDetailView.WhatsNew.versionHistory", fallback: "Version History")
|
||||
}
|
||||
}
|
||||
internal enum AppIDsView {
|
||||
/// Each app and app extension installed with SideStore must register an App ID with Apple.
|
||||
///
|
||||
/// App IDs for paid developer accounts never expire, and there is no limit to how many you can create.
|
||||
internal static let description = L10n.tr("Localizable", "AppIDsView.description", fallback: "Each app and app extension installed with SideStore must register an App ID with Apple.\n\nApp IDs for paid developer accounts never expire, and there is no limit to how many you can create.")
|
||||
/// AppIDsView
|
||||
internal static let title = L10n.tr("Localizable", "AppIDsView.title", fallback: "App IDs")
|
||||
}
|
||||
internal enum AppPermissionGrid {
|
||||
/// AppPermissionGrid
|
||||
internal static let usageDescription = L10n.tr("Localizable", "AppPermissionGrid.usageDescription", fallback: "Usage Description")
|
||||
}
|
||||
internal enum AppPillButton {
|
||||
/// AppPillButton
|
||||
internal static let free = L10n.tr("Localizable", "AppPillButton.free", fallback: "Free")
|
||||
/// Open
|
||||
internal static let `open` = L10n.tr("Localizable", "AppPillButton.open", fallback: "Open")
|
||||
}
|
||||
internal enum AppRowView {
|
||||
/// AppRowView
|
||||
internal static let sideloaded = L10n.tr("Localizable", "AppRowView.sideloaded", fallback: "Sideloaded")
|
||||
}
|
||||
internal enum BrowseView {
|
||||
/// Search
|
||||
internal static let search = L10n.tr("Localizable", "BrowseView.search", fallback: "Search")
|
||||
/// BrowseView
|
||||
internal static let title = L10n.tr("Localizable", "BrowseView.title", fallback: "Browse")
|
||||
internal enum Actions {
|
||||
/// Sources
|
||||
internal static let sources = L10n.tr("Localizable", "BrowseView.Actions.sources", fallback: "Sources")
|
||||
}
|
||||
internal enum Categories {
|
||||
/// Games and
|
||||
/// Emulators
|
||||
internal static let gamesAndEmulators = L10n.tr("Localizable", "BrowseView.Categories.gamesAndEmulators", fallback: "Games and\nEmulators")
|
||||
}
|
||||
internal enum Hints {
|
||||
internal enum NoApps {
|
||||
/// Add Source
|
||||
internal static let addSource = L10n.tr("Localizable", "BrowseView.Hints.NoApps.addSource", fallback: "Add Source")
|
||||
/// Apps are provided by "sources". The specification for them is an open standard, so everyone can create their own source. To get you started, we have compiled a list of "Trusted Sources" which you can check out by tapping the button below.
|
||||
internal static let text = L10n.tr("Localizable", "BrowseView.Hints.NoApps.text", fallback: "Apps are provided by \"sources\". The specification for them is an open standard, so everyone can create their own source. To get you started, we have compiled a list of \"Trusted Sources\" which you can check out by tapping the button below.")
|
||||
/// You don't have any apps yet.
|
||||
internal static let title = L10n.tr("Localizable", "BrowseView.Hints.NoApps.title", fallback: "You don't have any apps yet.")
|
||||
}
|
||||
}
|
||||
internal enum Section {
|
||||
internal enum AllApps {
|
||||
/// All Apps
|
||||
internal static let title = L10n.tr("Localizable", "BrowseView.Section.AllApps.title", fallback: "All Apps")
|
||||
}
|
||||
internal enum PromotedCategories {
|
||||
/// Show all
|
||||
internal static let showAll = L10n.tr("Localizable", "BrowseView.Section.PromotedCategories.showAll", fallback: "Show all")
|
||||
/// Promoted Categories
|
||||
internal static let title = L10n.tr("Localizable", "BrowseView.Section.PromotedCategories.title", fallback: "Promoted Categories")
|
||||
}
|
||||
}
|
||||
}
|
||||
internal enum ConfirmAddSourceView {
|
||||
/// Add Source
|
||||
internal static let addSource = L10n.tr("Localizable", "ConfirmAddSourceView.addSource", fallback: "Add Source")
|
||||
/// ConfirmAddSourceView
|
||||
internal static let apps = L10n.tr("Localizable", "ConfirmAddSourceView.apps", fallback: "Apps")
|
||||
/// News Items
|
||||
internal static let newsItems = L10n.tr("Localizable", "ConfirmAddSourceView.newsItems", fallback: "News Items")
|
||||
/// Source Contents
|
||||
internal static let sourceContents = L10n.tr("Localizable", "ConfirmAddSourceView.sourceContents", fallback: "Source Contents")
|
||||
/// Source Identifier
|
||||
internal static let sourceIdentifier = L10n.tr("Localizable", "ConfirmAddSourceView.sourceIdentifier", fallback: "Source Identifier")
|
||||
/// Source Information
|
||||
internal static let sourceInfo = L10n.tr("Localizable", "ConfirmAddSourceView.sourceInfo", fallback: "Source Information")
|
||||
/// Source URL
|
||||
internal static let sourceURL = L10n.tr("Localizable", "ConfirmAddSourceView.sourceURL", fallback: "Source URL")
|
||||
}
|
||||
internal enum ConnectAppleIDView {
|
||||
/// Apple ID
|
||||
internal static let appleID = L10n.tr("Localizable", "ConnectAppleIDView.appleID", fallback: "Apple ID")
|
||||
/// Cancel
|
||||
internal static let cancel = L10n.tr("Localizable", "ConnectAppleIDView.cancel", fallback: "Cancel")
|
||||
/// Connect Your Apple ID
|
||||
internal static let connectYourAppleID = L10n.tr("Localizable", "ConnectAppleIDView.connectYourAppleID", fallback: "Connect Your Apple ID")
|
||||
/// Failed to Sign In
|
||||
internal static let failedToSignIn = L10n.tr("Localizable", "ConnectAppleIDView.failedToSignIn", fallback: "Failed to Sign In")
|
||||
/// Your Apple ID is used to configure apps so they can be installed on this device. Your credentials will be stored securely in this device's Keychain and sent only to Apple for authentication.
|
||||
internal static let footer = L10n.tr("Localizable", "ConnectAppleIDView.footer", fallback: "Your Apple ID is used to configure apps so they can be installed on this device. Your credentials will be stored securely in this device's Keychain and sent only to Apple for authentication.")
|
||||
/// Password
|
||||
internal static let password = L10n.tr("Localizable", "ConnectAppleIDView.password", fallback: "Password")
|
||||
/// Sign In
|
||||
internal static let signIn = L10n.tr("Localizable", "ConnectAppleIDView.signIn", fallback: "Sign In")
|
||||
/// ConnectAppleIDView
|
||||
internal static let startWithSignIn = L10n.tr("Localizable", "ConnectAppleIDView.startWithSignIn", fallback: "Sign in with your Apple ID to get started.")
|
||||
/// Why do we need this?
|
||||
internal static let whyDoWeNeedThis = L10n.tr("Localizable", "ConnectAppleIDView.whyDoWeNeedThis", fallback: "Why do we need this?")
|
||||
}
|
||||
internal enum MyAppsView {
|
||||
/// MyAppsView
|
||||
internal static let active = L10n.tr("Localizable", "MyAppsView.active", fallback: "Active")
|
||||
/// App IDs Remaining
|
||||
internal static let appIDsRemaining = L10n.tr("Localizable", "MyAppsView.appIDsRemaining", fallback: "App IDs Remaining")
|
||||
/// apps
|
||||
internal static let apps = L10n.tr("Localizable", "MyAppsView.apps", fallback: "apps")
|
||||
/// Failed to refresh
|
||||
internal static let failedToRefresh = L10n.tr("Localizable", "MyAppsView.failedToRefresh", fallback: "Failed to refresh")
|
||||
/// My Apps
|
||||
internal static let myApps = L10n.tr("Localizable", "MyAppsView.myApps", fallback: "My Apps")
|
||||
/// Refresh All
|
||||
internal static let refreshAll = L10n.tr("Localizable", "MyAppsView.refreshAll", fallback: "Refresh All")
|
||||
/// Sideloading in progress...
|
||||
internal static let sideloading = L10n.tr("Localizable", "MyAppsView.sideloading", fallback: "Sideloading in progress...")
|
||||
/// Keep this lowercase
|
||||
internal static let viewAppIDs = L10n.tr("Localizable", "MyAppsView.viewAppIDs", fallback: "View App IDs")
|
||||
internal enum Hints {
|
||||
internal enum NoUpdates {
|
||||
/// Dismiss for now
|
||||
internal static let dismissForNow = L10n.tr("Localizable", "MyAppsView.Hints.NoUpdates.dismissForNow", fallback: "Dismiss for now")
|
||||
/// Don't show this again
|
||||
internal static let dontShowAgain = L10n.tr("Localizable", "MyAppsView.Hints.NoUpdates.dontShowAgain", fallback: "Don't show this again")
|
||||
/// You will be notified once updates for your apps are available. The updates will then be shown here.
|
||||
internal static let text = L10n.tr("Localizable", "MyAppsView.Hints.NoUpdates.text", fallback: "You will be notified once updates for your apps are available. The updates will then be shown here.")
|
||||
/// All Apps are Up To Date
|
||||
internal static let title = L10n.tr("Localizable", "MyAppsView.Hints.NoUpdates.title", fallback: "All Apps are Up To Date")
|
||||
}
|
||||
}
|
||||
}
|
||||
internal enum NewsView {
|
||||
/// NewsView
|
||||
internal static let title = L10n.tr("Localizable", "NewsView.title", fallback: "News")
|
||||
internal enum Section {
|
||||
internal enum FromSources {
|
||||
/// From your Sources
|
||||
internal static let title = L10n.tr("Localizable", "NewsView.Section.FromSources.title", fallback: "From your Sources")
|
||||
}
|
||||
}
|
||||
}
|
||||
internal enum RootView {
|
||||
/// Browse
|
||||
internal static let browse = L10n.tr("Localizable", "RootView.browse", fallback: "Browse")
|
||||
/// My Apps
|
||||
internal static let myApps = L10n.tr("Localizable", "RootView.myApps", fallback: "My Apps")
|
||||
/// RootView
|
||||
internal static let news = L10n.tr("Localizable", "RootView.news", fallback: "News")
|
||||
/// Settings
|
||||
internal static let settings = L10n.tr("Localizable", "RootView.settings", fallback: "Settings")
|
||||
}
|
||||
internal enum SettingsView {
|
||||
/// Add to Siri...
|
||||
internal static let addToSiri = L10n.tr("Localizable", "SettingsView.addToSiri", fallback: "Add to Siri...")
|
||||
/// Background Refresh
|
||||
internal static let backgroundRefresh = L10n.tr("Localizable", "SettingsView.backgroundRefresh", fallback: "Background Refresh")
|
||||
/// Connect your Apple ID
|
||||
internal static let connectAppleID = L10n.tr("Localizable", "SettingsView.connectAppleID", fallback: "Connect your Apple ID")
|
||||
/// Credits
|
||||
internal static let credits = L10n.tr("Localizable", "SettingsView.credits", fallback: "Credits")
|
||||
/// Debug
|
||||
internal static let debug = L10n.tr("Localizable", "SettingsView.debug", fallback: "Debug")
|
||||
/// Refreshing Apps
|
||||
internal static let refreshingApps = L10n.tr("Localizable", "SettingsView.refreshingApps", fallback: "Refreshing Apps")
|
||||
/// Enable Background Refresh to automatically refresh apps in the background when connected to WiFi and with Wireguard active.
|
||||
internal static let refreshingAppsFooter = L10n.tr("Localizable", "SettingsView.refreshingAppsFooter", fallback: "Enable Background Refresh to automatically refresh apps in the background when connected to WiFi and with Wireguard active.")
|
||||
/// Reset Image Cache
|
||||
internal static let resetImageCache = L10n.tr("Localizable", "SettingsView.resetImageCache", fallback: "Reset Image Cache")
|
||||
/// SwiftUI Redesign
|
||||
internal static let swiftUIRedesign = L10n.tr("Localizable", "SettingsView.swiftUIRedesign", fallback: "SwiftUI Redesign")
|
||||
/// Switch to UIKit
|
||||
internal static let switchToUIKit = L10n.tr("Localizable", "SettingsView.switchToUIKit", fallback: "Switch to UIKit")
|
||||
/// Settings
|
||||
internal static let title = L10n.tr("Localizable", "SettingsView.title", fallback: "Settings")
|
||||
internal enum ConnectedAppleID {
|
||||
/// E-Mail
|
||||
internal static let eMail = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.eMail", fallback: "E-Mail")
|
||||
/// SettingsView
|
||||
internal static let name = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.name", fallback: "Name")
|
||||
/// Sign Out
|
||||
internal static let signOut = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.signOut", fallback: "Sign Out")
|
||||
/// Connected Apple ID
|
||||
internal static let text = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.text", fallback: "Connected Apple ID")
|
||||
/// Type
|
||||
internal static let type = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.type", fallback: "Type")
|
||||
internal enum Footer {
|
||||
/// Your Apple ID is required to sign the apps you install with SideStore.
|
||||
internal static let p1 = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.Footer.p1", fallback: "Your Apple ID is required to sign the apps you install with SideStore.")
|
||||
/// Your credentials are only sent to Apple's servers and are not accessible by the SideStore Team. Once successfully logged in, the login details are stored securely on your device.
|
||||
internal static let p2 = L10n.tr("Localizable", "SettingsView.ConnectedAppleID.Footer.p2", fallback: "Your credentials are only sent to Apple's servers and are not accessible by the SideStore Team. Once successfully logged in, the login details are stored securely on your device.")
|
||||
}
|
||||
}
|
||||
}
|
||||
internal enum SourcesView {
|
||||
/// Done
|
||||
internal static let done = L10n.tr("Localizable", "SourcesView.done", fallback: "Done")
|
||||
/// Remove
|
||||
internal static let remove = L10n.tr("Localizable", "SourcesView.remove", fallback: "Remove")
|
||||
/// SideStore has reviewed these sources to make sure they meet our safety standards.
|
||||
internal static let reviewedText = L10n.tr("Localizable", "SourcesView.reviewedText", fallback: "SideStore has reviewed these sources to make sure they meet our safety standards.")
|
||||
/// Sources
|
||||
internal static let sources = L10n.tr("Localizable", "SourcesView.sources", fallback: "Sources")
|
||||
/// SourcesView
|
||||
internal static let sourcesDescription = L10n.tr("Localizable", "SourcesView.sourcesDescription", fallback: "Sources control what apps are available to download through SideStore.")
|
||||
/// Trusted Sources
|
||||
internal static let trustedSources = L10n.tr("Localizable", "SourcesView.trustedSources", fallback: "Trusted Sources")
|
||||
}
|
||||
}
|
||||
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
||||
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||
|
||||
// MARK: - Implementation Details
|
||||
|
||||
extension L10n {
|
||||
private static func tr(_ table: String, _ key: String, _ args: CVarArg..., fallback value: String) -> String {
|
||||
let format = BundleToken.bundle.localizedString(forKey: key, value: value, table: table)
|
||||
return String(format: format, locale: Locale.current, arguments: args)
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable convenience_type
|
||||
private final class BundleToken {
|
||||
static let bundle: Bundle = {
|
||||
#if SWIFT_PACKAGE
|
||||
return Bundle.module
|
||||
#else
|
||||
return Bundle(for: BundleToken.self)
|
||||
#endif
|
||||
}()
|
||||
}
|
||||
// swiftlint:enable convenience_type
|
||||
72
AltStore/Helper/DateFormatterHelper.swift
Normal file
72
AltStore/Helper/DateFormatterHelper.swift
Normal file
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// DateFormatterHelper.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 20.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct DateFormatterHelper {
|
||||
|
||||
private static let appExpirationDateFormatter: DateComponentsFormatter = {
|
||||
let dateComponentsFormatter = DateComponentsFormatter()
|
||||
dateComponentsFormatter.zeroFormattingBehavior = [.pad]
|
||||
dateComponentsFormatter.collapsesLargestUnit = false
|
||||
return dateComponentsFormatter
|
||||
}()
|
||||
|
||||
private static let relativeDateFormatter: RelativeDateTimeFormatter = {
|
||||
let formatter = RelativeDateTimeFormatter()
|
||||
formatter.unitsStyle = .abbreviated
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private static let mediumDateFormatter: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .none
|
||||
dateFormatter.doesRelativeDateFormatting = true
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
private static let timeFormatter: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .none
|
||||
dateFormatter.timeStyle = .short
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
|
||||
|
||||
static func string(forExpirationDate date: Date) -> String {
|
||||
let startDate = Date()
|
||||
let interval = date.timeIntervalSince(startDate)
|
||||
guard interval > 0 else {
|
||||
return "EXPIRED"
|
||||
}
|
||||
|
||||
if interval < (24 * 60 * 60) {
|
||||
self.appExpirationDateFormatter.unitsStyle = .positional
|
||||
self.appExpirationDateFormatter.allowedUnits = [.minute, .second]
|
||||
} else {
|
||||
self.appExpirationDateFormatter.unitsStyle = .full
|
||||
self.appExpirationDateFormatter.allowedUnits = [.day]
|
||||
}
|
||||
|
||||
return self.appExpirationDateFormatter.string(from: startDate, to: date) ?? ""
|
||||
}
|
||||
|
||||
static func string(forRelativeDate date: Date, to referenceDate: Date = Date()) -> String {
|
||||
self.relativeDateFormatter.localizedString(for: date, relativeTo: referenceDate)
|
||||
}
|
||||
|
||||
static func string(for date: Date) -> String {
|
||||
self.mediumDateFormatter.string(from: date)
|
||||
}
|
||||
|
||||
static func timeString(for date: Date) -> String {
|
||||
self.timeFormatter.string(from: date)
|
||||
}
|
||||
}
|
||||
254
AltStore/Helper/SideloadingManager.swift
Normal file
254
AltStore/Helper/SideloadingManager.swift
Normal file
@@ -0,0 +1,254 @@
|
||||
//
|
||||
// SideloadingManager.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 20.12.22.
|
||||
// Copyright © 2022 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
import AltStoreCore
|
||||
import CAltSign
|
||||
import Roxas
|
||||
|
||||
// TODO: Move this to the AppManager
|
||||
class SideloadingManager {
|
||||
class Context {
|
||||
var fileURL: URL?
|
||||
var application: ALTApplication?
|
||||
var installedApp: InstalledApp? {
|
||||
didSet {
|
||||
self.installedAppContext = self.installedApp?.managedObjectContext
|
||||
}
|
||||
}
|
||||
private var installedAppContext: NSManagedObjectContext?
|
||||
var error: Error?
|
||||
}
|
||||
|
||||
|
||||
public static let shared = SideloadingManager()
|
||||
|
||||
@Published
|
||||
public var progress: Progress?
|
||||
|
||||
private let operationQueue = OperationQueue()
|
||||
|
||||
private init() {}
|
||||
|
||||
|
||||
// TODO: Refactor & convert to async
|
||||
func sideloadApp(at url: URL, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
self.progress = Progress.discreteProgress(totalUnitCount: 100)
|
||||
|
||||
let temporaryDirectory = FileManager.default.uniqueTemporaryURL()
|
||||
let unzippedAppDirectory = temporaryDirectory.appendingPathComponent("App")
|
||||
|
||||
let context = Context()
|
||||
|
||||
let downloadOperation: RSTAsyncBlockOperation?
|
||||
|
||||
if url.isFileURL {
|
||||
downloadOperation = nil
|
||||
context.fileURL = url
|
||||
self.progress?.totalUnitCount -= 20
|
||||
} else {
|
||||
let downloadProgress = Progress.discreteProgress(totalUnitCount: 100)
|
||||
|
||||
downloadOperation = RSTAsyncBlockOperation { (operation) in
|
||||
let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in
|
||||
do
|
||||
{
|
||||
let (fileURL, _) = try Result((fileURL, response), error).get()
|
||||
|
||||
try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
let destinationURL = temporaryDirectory.appendingPathComponent("App.ipa")
|
||||
try FileManager.default.moveItem(at: fileURL, to: destinationURL)
|
||||
|
||||
context.fileURL = destinationURL
|
||||
}
|
||||
catch
|
||||
{
|
||||
context.error = error
|
||||
}
|
||||
operation.finish()
|
||||
}
|
||||
downloadProgress.addChild(downloadTask.progress, withPendingUnitCount: 100)
|
||||
downloadTask.resume()
|
||||
}
|
||||
self.progress?.addChild(downloadProgress, withPendingUnitCount: 20)
|
||||
}
|
||||
|
||||
let unzipProgress = Progress.discreteProgress(totalUnitCount: 1)
|
||||
let unzipAppOperation = BlockOperation {
|
||||
do
|
||||
{
|
||||
if let error = context.error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let fileURL = context.fileURL else { throw OperationError.invalidParameters }
|
||||
defer {
|
||||
try? FileManager.default.removeItem(at: fileURL)
|
||||
}
|
||||
|
||||
try FileManager.default.createDirectory(at: unzippedAppDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||
let unzippedApplicationURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: unzippedAppDirectory)
|
||||
|
||||
guard let application = ALTApplication(fileURL: unzippedApplicationURL) else { throw OperationError.invalidApp }
|
||||
context.application = application
|
||||
|
||||
unzipProgress.completedUnitCount = 1
|
||||
}
|
||||
catch
|
||||
{
|
||||
context.error = error
|
||||
}
|
||||
}
|
||||
self.progress?.addChild(unzipProgress, withPendingUnitCount: 10)
|
||||
|
||||
if let downloadOperation = downloadOperation
|
||||
{
|
||||
unzipAppOperation.addDependency(downloadOperation)
|
||||
}
|
||||
|
||||
let removeAppExtensionsProgress = Progress.discreteProgress(totalUnitCount: 1)
|
||||
|
||||
let removeAppExtensionsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
||||
do
|
||||
{
|
||||
if let error = context.error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let application = context.application else { throw OperationError.invalidParameters }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self?.removeAppExtensions(from: application) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .success: removeAppExtensionsProgress.completedUnitCount = 1
|
||||
case .failure(let error): context.error = error
|
||||
}
|
||||
operation.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
context.error = error
|
||||
operation.finish()
|
||||
}
|
||||
}
|
||||
removeAppExtensionsOperation.addDependency(unzipAppOperation)
|
||||
self.progress?.addChild(removeAppExtensionsProgress, withPendingUnitCount: 5)
|
||||
|
||||
let installProgress = Progress.discreteProgress(totalUnitCount: 100)
|
||||
let installAppOperation = RSTAsyncBlockOperation { (operation) in
|
||||
do
|
||||
{
|
||||
if let error = context.error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let application = context.application else { throw OperationError.invalidParameters }
|
||||
|
||||
let group = AppManager.shared.install(application, presentingViewController: nil) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .success(let installedApp): context.installedApp = installedApp
|
||||
case .failure(let error): context.error = error
|
||||
}
|
||||
operation.finish()
|
||||
}
|
||||
installProgress.addChild(group.progress, withPendingUnitCount: 100)
|
||||
}
|
||||
catch
|
||||
{
|
||||
context.error = error
|
||||
operation.finish()
|
||||
}
|
||||
}
|
||||
installAppOperation.completionBlock = {
|
||||
try? FileManager.default.removeItem(at: temporaryDirectory)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.progress = nil
|
||||
|
||||
switch Result(context.installedApp, context.error)
|
||||
{
|
||||
case .success(let app):
|
||||
completion(.success(()))
|
||||
|
||||
app.managedObjectContext?.perform {
|
||||
print("Successfully installed app:", app.bundleIdentifier)
|
||||
}
|
||||
|
||||
case .failure(OperationError.cancelled):
|
||||
completion(.failure((OperationError.cancelled)))
|
||||
|
||||
case .failure(let error):
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
self.progress?.addChild(installProgress, withPendingUnitCount: 65)
|
||||
installAppOperation.addDependency(removeAppExtensionsOperation)
|
||||
|
||||
let operations = [downloadOperation, unzipAppOperation, removeAppExtensionsOperation, installAppOperation].compactMap { $0 }
|
||||
self.operationQueue.addOperations(operations, waitUntilFinished: false)
|
||||
}
|
||||
|
||||
|
||||
// TODO: Refactor
|
||||
private func removeAppExtensions(from application: ALTApplication, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
guard !application.appExtensions.isEmpty else { return completion(.success(())) }
|
||||
|
||||
let firstSentence: String
|
||||
|
||||
if UserDefaults.standard.activeAppLimitIncludesExtensions
|
||||
{
|
||||
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions.", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to creating 10 App IDs per week.", comment: "")
|
||||
}
|
||||
|
||||
let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit?", comment: "")
|
||||
|
||||
let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
|
||||
completion(.failure(OperationError.cancelled))
|
||||
}))
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in
|
||||
completion(.success(()))
|
||||
})
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in
|
||||
do
|
||||
{
|
||||
for appExtension in application.appExtensions
|
||||
{
|
||||
try FileManager.default.removeItem(at: appExtension.fileURL)
|
||||
}
|
||||
|
||||
completion(.success(()))
|
||||
}
|
||||
catch
|
||||
{
|
||||
completion(.failure(error))
|
||||
}
|
||||
})
|
||||
|
||||
let rootViewController = UIApplication.shared.keyWindow?.rootViewController
|
||||
rootViewController?.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>ALTAnisetteURL</key>
|
||||
<string>https://ani.sidestore.io</string>
|
||||
<key>ALTAppGroups</key>
|
||||
<array>
|
||||
<string>group.$(APP_GROUP_IDENTIFIER)</string>
|
||||
@@ -11,10 +9,12 @@
|
||||
</array>
|
||||
<key>ALTDeviceID</key>
|
||||
<string>00008101-000129D63698001E</string>
|
||||
<key>ALTPairingFile</key>
|
||||
<string><insert pairing file here></string>
|
||||
<key>ALTServerID</key>
|
||||
<string>1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D</string>
|
||||
<key>ALTPairingFile</key>
|
||||
<string><insert pairing file here></string>
|
||||
<key>ALTAnisetteURL</key>
|
||||
<string>https://ani.sidestore.io</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
@@ -44,6 +44,8 @@
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
@@ -91,13 +93,6 @@
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_altserver._tcp</string>
|
||||
@@ -136,10 +131,13 @@
|
||||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
@@ -206,5 +204,7 @@
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import Roxas
|
||||
import EmotionalDamage
|
||||
import minimuxer
|
||||
@@ -42,23 +43,41 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
{
|
||||
defer {
|
||||
// Create destinationViewController now so view controllers can register for receiving Notifications.
|
||||
self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController
|
||||
// self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController
|
||||
let rootView = RootView()
|
||||
.environment(\.managedObjectContext, DatabaseManager.shared.viewContext)
|
||||
self.destinationViewController = UIHostingController(rootView: rootView)
|
||||
}
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(true)
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
if !UserDefaults.standard.onboardingComplete {
|
||||
self.showOnboarding()
|
||||
return
|
||||
}
|
||||
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
|
||||
guard let pf = fetchPairingFile() else {
|
||||
displayError("Device pairing file not found.")
|
||||
self.showOnboarding(enabledSteps: [.pairing])
|
||||
return
|
||||
}
|
||||
start_minimuxer_threads(pf)
|
||||
#endif
|
||||
}
|
||||
|
||||
func showOnboarding(enabledSteps: [OnboardingStep] = OnboardingStep.allCases) {
|
||||
let onboardingView = OnboardingView(onDismiss: { self.dismiss(animated: true) }, enabledSteps: enabledSteps)
|
||||
.environment(\.managedObjectContext, DatabaseManager.shared.viewContext)
|
||||
let navigationController = UINavigationController(rootViewController: UIHostingController(rootView: onboardingView))
|
||||
navigationController.isNavigationBarHidden = true
|
||||
navigationController.isModalInPresentation = true
|
||||
self.present(navigationController, animated: true)
|
||||
}
|
||||
|
||||
func fetchPairingFile() -> String? {
|
||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||
@@ -72,17 +91,16 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
fm.fileExists(atPath: appResourcePath.path),
|
||||
let data = fm.contents(atPath: appResourcePath.path),
|
||||
let contents = String(data: data, encoding: .utf8),
|
||||
!contents.isEmpty,
|
||||
!UserDefaults.standard.isPairingReset {
|
||||
!contents.isEmpty {
|
||||
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{
|
||||
} else if let plistString = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String, !plistString.isEmpty, !plistString.contains("insert pairing file here"){
|
||||
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 for your device. For more information, go to https://wiki.sidestore.io/guides/getting-started/#pairing-file", preferredStyle: .alert)
|
||||
let dialogMessage = UIAlertController(title: "Pairing File", message: "Select the pairing file for your device. For more information, go to https://wiki.sidestore.io/guides/install#pairing-process", preferredStyle: .alert)
|
||||
|
||||
// Create OK button with action handler
|
||||
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
|
||||
@@ -94,7 +112,6 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
documentPickerController.shouldShowFileExtensions = true
|
||||
documentPickerController.delegate = self
|
||||
self.present(documentPickerController, animated: true, completion: nil)
|
||||
UserDefaults.standard.isPairingReset = false
|
||||
})
|
||||
|
||||
//Add OK button to a dialog message
|
||||
@@ -103,13 +120,6 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -164,12 +174,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
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!")")
|
||||
}
|
||||
if #available(iOS 17, *) {
|
||||
// TODO: iOS 17 and above have a new JIT implementation that is completely broken in SideStore :(
|
||||
}
|
||||
else {
|
||||
start_auto_mounter(documentsDirectory)
|
||||
}
|
||||
start_auto_mounter(documentsDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
80
AltStore/Manager/NotificationManager.swift
Normal file
80
AltStore/Manager/NotificationManager.swift
Normal file
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// NotificationManager.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 21.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AltStoreCore
|
||||
|
||||
class NotificationManager: ObservableObject {
|
||||
|
||||
struct Notification: Identifiable {
|
||||
let id: UUID
|
||||
let title: String
|
||||
let message: String?
|
||||
}
|
||||
|
||||
static let shared = NotificationManager()
|
||||
|
||||
@Published
|
||||
var notifications: [UUID: Notification] = [:]
|
||||
|
||||
private init() {}
|
||||
|
||||
func reportError(error: Error) {
|
||||
if case OperationError.cancelled = error {
|
||||
// Ignore
|
||||
return
|
||||
}
|
||||
|
||||
var error = error as NSError
|
||||
var underlyingError = error.underlyingError
|
||||
|
||||
if
|
||||
let unwrappedUnderlyingError = underlyingError,
|
||||
error.domain == AltServerErrorDomain && error.code == ALTServerError.Code.underlyingError.rawValue
|
||||
{
|
||||
// Treat underlyingError as the primary error.
|
||||
|
||||
error = unwrappedUnderlyingError as NSError
|
||||
underlyingError = nil
|
||||
}
|
||||
|
||||
let text: String
|
||||
let detailText: String?
|
||||
|
||||
if let failure = error.localizedFailure
|
||||
{
|
||||
text = failure
|
||||
detailText = error.localizedFailureReason ?? error.localizedRecoverySuggestion ?? underlyingError?.localizedDescription ?? error.localizedDescription
|
||||
}
|
||||
else if let reason = error.localizedFailureReason
|
||||
{
|
||||
text = reason
|
||||
detailText = error.localizedRecoverySuggestion ?? underlyingError?.localizedDescription
|
||||
}
|
||||
else
|
||||
{
|
||||
text = error.localizedDescription
|
||||
detailText = underlyingError?.localizedDescription ?? error.localizedRecoverySuggestion
|
||||
}
|
||||
|
||||
self.showNotification(title: text, detailText: detailText)
|
||||
}
|
||||
|
||||
func showNotification(title: String, detailText: String? = nil) {
|
||||
let notificationId = UUID()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.notifications[notificationId] = Notification(id: notificationId, title: title, message: detailText)
|
||||
}
|
||||
|
||||
let dismissWorkItem = DispatchWorkItem {
|
||||
self.notifications.removeValue(forKey: notificationId)
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now().advanced(by: .seconds(5)), execute: dismissWorkItem)
|
||||
}
|
||||
}
|
||||
56
AltStore/Manager/OutputCapturer.swift
Normal file
56
AltStore/Manager/OutputCapturer.swift
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// OutputCapturer.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 12.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LocalConsole
|
||||
|
||||
class OutputCapturer {
|
||||
|
||||
public static let shared = OutputCapturer()
|
||||
|
||||
private let consoleManager = LCManager.shared
|
||||
|
||||
private var inputPipe = Pipe()
|
||||
private var errorPipe = Pipe()
|
||||
private var outputPipe = Pipe()
|
||||
|
||||
private init() {
|
||||
// Setup pipe file handlers
|
||||
self.inputPipe.fileHandleForReading.readabilityHandler = { [weak self] fileHandle in
|
||||
self?.handle(data: fileHandle.availableData)
|
||||
}
|
||||
self.errorPipe.fileHandleForReading.readabilityHandler = { [weak self] fileHandle in
|
||||
self?.handle(data: fileHandle.availableData, isError: true)
|
||||
}
|
||||
|
||||
// Keep STDOUT
|
||||
dup2(STDOUT_FILENO, self.outputPipe.fileHandleForWriting.fileDescriptor)
|
||||
|
||||
// Intercept STDOUT and STDERR
|
||||
dup2(self.inputPipe.fileHandleForWriting.fileDescriptor, STDOUT_FILENO)
|
||||
dup2(self.errorPipe.fileHandleForWriting.fileDescriptor, STDERR_FILENO)
|
||||
}
|
||||
|
||||
deinit {
|
||||
try? self.inputPipe.fileHandleForReading.close()
|
||||
try? self.errorPipe.fileHandleForReading.close()
|
||||
}
|
||||
|
||||
private func handle(data: Data, isError: Bool = false) {
|
||||
// Write output to STDOUT
|
||||
self.outputPipe.fileHandleForWriting.write(data)
|
||||
|
||||
guard let string = String(data: data, encoding: .utf8) else {
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.consoleManager.print(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,16 +307,6 @@ extension AppManager
|
||||
presentingViewController.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func clearAppCache(completion: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
let clearAppCacheOperation = ClearAppCacheOperation()
|
||||
clearAppCacheOperation.resultHandler = { result in
|
||||
completion(result)
|
||||
}
|
||||
|
||||
self.run([clearAppCacheOperation], context: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension AppManager
|
||||
@@ -764,12 +754,6 @@ extension AppManager
|
||||
let progress = self.refreshProgress[app.bundleIdentifier]
|
||||
return progress
|
||||
}
|
||||
|
||||
func isActivelyManagingApp(withBundleID bundleID: String) -> Bool
|
||||
{
|
||||
let isActivelyManaging = self.installationProgress.keys.contains(bundleID) || self.refreshProgress.keys.contains(bundleID)
|
||||
return isActivelyManaging
|
||||
}
|
||||
}
|
||||
|
||||
extension AppManager
|
||||
@@ -824,6 +808,12 @@ private extension AppManager
|
||||
}
|
||||
}
|
||||
|
||||
func isActivelyManagingApp(withBundleID bundleID: String) -> Bool
|
||||
{
|
||||
let isActivelyManaging = self.installationProgress.keys.contains(bundleID) || self.refreshProgress.keys.contains(bundleID)
|
||||
return isActivelyManaging
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func perform(_ operations: [AppOperation], presentingViewController: UIViewController?, group: RefreshGroup) -> RefreshGroup
|
||||
{
|
||||
@@ -958,13 +948,7 @@ private extension AppManager
|
||||
}
|
||||
else
|
||||
{
|
||||
DispatchQueue.main.schedule {
|
||||
UIApplication.shared.isIdleTimerDisabled = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||
}
|
||||
performAppOperations()
|
||||
DispatchQueue.main.schedule {
|
||||
UIApplication.shared.isIdleTimerDisabled = false
|
||||
}
|
||||
}
|
||||
|
||||
return group
|
||||
@@ -1043,32 +1027,6 @@ private extension AppManager
|
||||
verifyOperation.addDependency(downloadOperation)
|
||||
|
||||
|
||||
/* Refresh Anisette Data */
|
||||
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
|
||||
refreshAnisetteDataOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let anisetteData): group.context.session?.anisetteData = anisetteData
|
||||
}
|
||||
}
|
||||
refreshAnisetteDataOperation.addDependency(verifyOperation)
|
||||
|
||||
|
||||
/* Fetch Provisioning Profiles */
|
||||
let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context)
|
||||
fetchProvisioningProfilesOperation.additionalEntitlements = additionalEntitlements
|
||||
fetchProvisioningProfilesOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let provisioningProfiles): context.provisioningProfiles = provisioningProfiles
|
||||
}
|
||||
}
|
||||
fetchProvisioningProfilesOperation.addDependency(refreshAnisetteDataOperation)
|
||||
progress.addChild(fetchProvisioningProfilesOperation.progress, withPendingUnitCount: 5)
|
||||
|
||||
|
||||
/* Deactivate Apps (if necessary) */
|
||||
let deactivateAppsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
|
||||
do
|
||||
@@ -1084,12 +1042,6 @@ private extension AppManager
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let profiles = context.provisioningProfiles else { throw OperationError.invalidParameters }
|
||||
if !profiles.contains(where: { $1.isFreeProvisioningProfile == true }) {
|
||||
operation.finish()
|
||||
return
|
||||
}
|
||||
|
||||
guard let app = context.app, let presentingViewController = context.authenticatedContext.presentingViewController else { throw OperationError.invalidParameters }
|
||||
|
||||
@@ -1109,7 +1061,7 @@ private extension AppManager
|
||||
operation.finish()
|
||||
}
|
||||
}
|
||||
deactivateAppsOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||
deactivateAppsOperation.addDependency(verifyOperation)
|
||||
|
||||
|
||||
/* Patch App */
|
||||
@@ -1184,6 +1136,32 @@ private extension AppManager
|
||||
patchAppOperation.addDependency(deactivateAppsOperation)
|
||||
|
||||
|
||||
/* Refresh Anisette Data */
|
||||
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
|
||||
refreshAnisetteDataOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let anisetteData): group.context.session?.anisetteData = anisetteData
|
||||
}
|
||||
}
|
||||
refreshAnisetteDataOperation.addDependency(patchAppOperation)
|
||||
|
||||
|
||||
/* Fetch Provisioning Profiles */
|
||||
let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context)
|
||||
fetchProvisioningProfilesOperation.additionalEntitlements = additionalEntitlements
|
||||
fetchProvisioningProfilesOperation.resultHandler = { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): context.error = error
|
||||
case .success(let provisioningProfiles): context.provisioningProfiles = provisioningProfiles
|
||||
}
|
||||
}
|
||||
fetchProvisioningProfilesOperation.addDependency(refreshAnisetteDataOperation)
|
||||
progress.addChild(fetchProvisioningProfilesOperation.progress, withPendingUnitCount: 5)
|
||||
|
||||
|
||||
/* Resign */
|
||||
let resignAppOperation = ResignAppOperation(context: context)
|
||||
resignAppOperation.resultHandler = { (result) in
|
||||
@@ -1193,7 +1171,7 @@ private extension AppManager
|
||||
case .success(let resignedApp): context.resignedApp = resignedApp
|
||||
}
|
||||
}
|
||||
resignAppOperation.addDependency(patchAppOperation)
|
||||
resignAppOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
|
||||
|
||||
|
||||
@@ -1236,7 +1214,7 @@ private extension AppManager
|
||||
progress.addChild(installOperation.progress, withPendingUnitCount: 30)
|
||||
installOperation.addDependency(sendAppOperation)
|
||||
|
||||
let operations = [downloadOperation, verifyOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, deactivateAppsOperation, patchAppOperation, resignAppOperation, sendAppOperation, installOperation]
|
||||
let operations = [downloadOperation, verifyOperation, deactivateAppsOperation, patchAppOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, resignAppOperation, sendAppOperation, installOperation]
|
||||
group.add(operations)
|
||||
self.run(operations, context: group.context)
|
||||
|
||||
|
||||
@@ -10,12 +10,10 @@ import UIKit
|
||||
import MobileCoreServices
|
||||
import Intents
|
||||
import Combine
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
import minimuxer
|
||||
|
||||
import Nuke
|
||||
|
||||
@@ -329,25 +327,21 @@ private extension MyAppsViewController
|
||||
let currentDate = Date()
|
||||
|
||||
let numberOfDays = installedApp.expirationDate.numberOfCalendarDays(since: currentDate)
|
||||
let numberOfDaysText: String
|
||||
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.unitsStyle = .full
|
||||
formatter.includesApproximationPhrase = false
|
||||
formatter.includesTimeRemainingPhrase = false
|
||||
|
||||
formatter.allowedUnits = [.day, .hour, .minute]
|
||||
|
||||
formatter.maximumUnitCount = 1
|
||||
|
||||
|
||||
|
||||
cell.bannerView.button.setTitle(formatter.string(from: currentDate, to: installedApp.expirationDate)?.uppercased(), for: .normal)
|
||||
if numberOfDays == 1
|
||||
{
|
||||
numberOfDaysText = NSLocalizedString("1 day", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
numberOfDaysText = String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
||||
}
|
||||
|
||||
cell.bannerView.button.setTitle(numberOfDaysText.uppercased(), for: .normal)
|
||||
cell.bannerView.button.accessibilityLabel = String(format: NSLocalizedString("Refresh %@", comment: ""), installedApp.name)
|
||||
|
||||
formatter.includesTimeRemainingPhrase = true
|
||||
|
||||
cell.bannerView.accessibilityLabel? += ". " + (formatter.string(from: currentDate, to: installedApp.expirationDate) ?? NSLocalizedString("Unknown", comment: "")) + " "
|
||||
|
||||
cell.bannerView.accessibilityLabel? += ". " + String(format: NSLocalizedString("Expires in %@", comment: ""), numberOfDaysText)
|
||||
|
||||
// Make sure refresh button is correct size.
|
||||
cell.layoutIfNeeded()
|
||||
@@ -645,12 +639,6 @@ private extension MyAppsViewController
|
||||
|
||||
@IBAction func refreshAllApps(_ sender: UIBarButtonItem)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
self.isRefreshingAllApps = true
|
||||
self.collectionView.collectionViewLayout.invalidateLayout()
|
||||
|
||||
@@ -713,15 +701,18 @@ private extension MyAppsViewController
|
||||
|
||||
@IBAction func sideloadApp(_ sender: UIBarButtonItem)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
let supportedTypes = UTType.types(tag: "ipa", tagClass: .filenameExtension, conformingTo: nil)
|
||||
let supportedTypes: [String]
|
||||
|
||||
let documentPickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: supportedTypes, asCopy: true)
|
||||
if let types = UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension, "ipa" as CFString, nil)?.takeRetainedValue()
|
||||
{
|
||||
supportedTypes = (types as NSArray).map { $0 as! String }
|
||||
}
|
||||
else
|
||||
{
|
||||
supportedTypes = ["com.apple.itunes.ipa"] // Declared by the system.
|
||||
}
|
||||
|
||||
let documentPickerViewController = UIDocumentPickerViewController(documentTypes: supportedTypes, in: .import)
|
||||
documentPickerViewController.delegate = self
|
||||
self.present(documentPickerViewController, animated: true, completion: nil)
|
||||
}
|
||||
@@ -1023,12 +1014,6 @@ private extension MyAppsViewController
|
||||
|
||||
func refresh(_ installedApp: InstalledApp)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
let previousProgress = AppManager.shared.refreshProgress(for: installedApp)
|
||||
guard previousProgress == nil else {
|
||||
previousProgress?.cancel()
|
||||
@@ -1050,12 +1035,6 @@ private extension MyAppsViewController
|
||||
|
||||
func activate(_ installedApp: InstalledApp)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
func finish(_ result: Result<InstalledApp, Error>)
|
||||
{
|
||||
do
|
||||
@@ -1132,11 +1111,6 @@ private extension MyAppsViewController
|
||||
func deactivate(_ installedApp: InstalledApp, completionHandler: ((Result<InstalledApp, Error>) -> Void)? = nil)
|
||||
{
|
||||
guard installedApp.isActive else { return }
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
installedApp.isActive = false
|
||||
|
||||
AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in
|
||||
@@ -1198,11 +1172,6 @@ private extension MyAppsViewController
|
||||
|
||||
func backup(_ installedApp: InstalledApp)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
let title = NSLocalizedString("Start Backup?", comment: "")
|
||||
let message = NSLocalizedString("This will replace any previous backups. Please leave SideStore open until the backup is complete.", comment: "")
|
||||
|
||||
@@ -1242,11 +1211,6 @@ private extension MyAppsViewController
|
||||
|
||||
func restore(_ installedApp: InstalledApp)
|
||||
{
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name)
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet)
|
||||
alertController.addAction(.cancel)
|
||||
@@ -1282,11 +1246,8 @@ private extension MyAppsViewController
|
||||
{
|
||||
guard let backupURL = FileManager.default.backupDirectoryURL(for: installedApp) else { return }
|
||||
|
||||
let documentPicker = UIDocumentPickerViewController(forExporting: [backupURL], asCopy: true)
|
||||
|
||||
// Don't set delegate to avoid conflicting with import callbacks.
|
||||
// documentPicker.delegate = self
|
||||
|
||||
let documentPicker = UIDocumentPickerViewController(url: backupURL, in: .exportToService)
|
||||
documentPicker.delegate = self
|
||||
self.present(documentPicker, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@@ -1350,16 +1311,6 @@ private extension MyAppsViewController
|
||||
@available(iOS 14, *)
|
||||
func enableJIT(for installedApp: InstalledApp)
|
||||
{
|
||||
if #available(iOS 17, *) {
|
||||
let toastView = ToastView(error: OperationError.tooNewError)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
if !minimuxer.ready() {
|
||||
let toastView = ToastView(error: MinimuxerError.NoConnection)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
AppManager.shared.enableJIT(for: installedApp) { result in
|
||||
DispatchQueue.main.async {
|
||||
switch result
|
||||
@@ -1367,7 +1318,7 @@ private extension MyAppsViewController
|
||||
case .success: break
|
||||
case .failure(let error):
|
||||
let toastView = ToastView(error: error)
|
||||
toastView.show(in: self.navigationController?.view ?? self.view, duration: 5)
|
||||
toastView.show(in: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1514,7 +1465,7 @@ extension MyAppsViewController
|
||||
let registeredAppIDs = team.appIDs.count
|
||||
|
||||
let maximumAppIDCount = 10
|
||||
let remainingAppIDs = maximumAppIDCount - registeredAppIDs
|
||||
let remainingAppIDs = max(maximumAppIDCount - registeredAppIDs, 0)
|
||||
|
||||
if remainingAppIDs == 1
|
||||
{
|
||||
@@ -1525,7 +1476,7 @@ extension MyAppsViewController
|
||||
footerView.textLabel.text = String(format: NSLocalizedString("%@ App IDs Remaining", comment: ""), NSNumber(value: remainingAppIDs))
|
||||
}
|
||||
|
||||
footerView.textLabel.isHidden = remainingAppIDs < 0
|
||||
footerView.textLabel.isHidden = false
|
||||
|
||||
case .individual, .organization, .unknown: footerView.textLabel.isHidden = true
|
||||
@unknown default: break
|
||||
@@ -2099,8 +2050,15 @@ extension MyAppsViewController: UIDocumentPickerDelegate
|
||||
{
|
||||
guard let fileURL = urls.first else { return }
|
||||
|
||||
self.sideloadApp(at: fileURL) { (result) in
|
||||
print("Sideloaded app at \(fileURL) with result:", result)
|
||||
switch controller.documentPickerMode
|
||||
{
|
||||
case .import, .open:
|
||||
self.sideloadApp(at: fileURL) { (result) in
|
||||
print("Sideloaded app at \(fileURL) with result:", result)
|
||||
}
|
||||
|
||||
case .exportToService, .moveToService: break
|
||||
@unknown default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,10 +426,6 @@ extension NewsViewController: UICollectionViewDelegateFlowLayout
|
||||
return previousSize
|
||||
}
|
||||
|
||||
// Take layout margins into account.
|
||||
self.prototypeCell.layoutMargins.left = self.view.layoutMargins.left
|
||||
self.prototypeCell.layoutMargins.right = self.view.layoutMargins.right
|
||||
|
||||
let widthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionView.bounds.width)
|
||||
NSLayoutConstraint.activate([widthConstraint])
|
||||
defer { NSLayoutConstraint.deactivate([widthConstraint]) }
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import Roxas
|
||||
import Network
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import minimuxer
|
||||
|
||||
enum AuthenticationError: LocalizedError
|
||||
{
|
||||
@@ -40,17 +40,17 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
||||
let context: AuthenticatedOperationContext
|
||||
|
||||
private weak var presentingViewController: UIViewController?
|
||||
|
||||
private lazy var navigationController: UINavigationController = {
|
||||
let navigationController = self.storyboard.instantiateViewController(withIdentifier: "navigationController") as! UINavigationController
|
||||
if #available(iOS 13.0, *)
|
||||
{
|
||||
navigationController.isModalInPresentation = true
|
||||
}
|
||||
return navigationController
|
||||
}()
|
||||
|
||||
private lazy var storyboard = UIStoryboard(name: "Authentication", bundle: nil)
|
||||
|
||||
// private lazy var navigationController: UINavigationController = {
|
||||
// let navigationController = self.storyboard.instantiateViewController(withIdentifier: "navigationController") as! UINavigationController
|
||||
// if #available(iOS 13.0, *)
|
||||
// {
|
||||
// navigationController.isModalInPresentation = true
|
||||
// }
|
||||
// return navigationController
|
||||
// }()
|
||||
//
|
||||
// private lazy var storyboard = UIStoryboard(name: "Authentication", bundle: nil)
|
||||
|
||||
private var appleIDEmailAddress: String?
|
||||
private var appleIDPassword: String?
|
||||
@@ -267,7 +267,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
||||
super.finish(result)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.navigationController.dismiss(animated: true, completion: nil)
|
||||
// self.navigationController.dismiss(animated: true, completion: nil)
|
||||
self.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,7 +278,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
|
||||
super.finish(result)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.navigationController.dismiss(animated: true, completion: nil)
|
||||
// self.navigationController.dismiss(animated: true, completion: nil)
|
||||
self.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,25 +290,33 @@ private extension AuthenticationOperation
|
||||
{
|
||||
func present(_ viewController: UIViewController) -> Bool
|
||||
{
|
||||
guard let presentingViewController = self.presentingViewController else { return false }
|
||||
|
||||
self.navigationController.view.tintColor = .white
|
||||
|
||||
if self.navigationController.viewControllers.isEmpty
|
||||
{
|
||||
guard presentingViewController.presentedViewController == nil else { return false }
|
||||
|
||||
self.navigationController.setViewControllers([viewController], animated: false)
|
||||
presentingViewController.present(self.navigationController, animated: true, completion: nil)
|
||||
}
|
||||
else
|
||||
{
|
||||
viewController.navigationItem.leftBarButtonItem = nil
|
||||
self.navigationController.pushViewController(viewController, animated: true)
|
||||
}
|
||||
UIApplication.shared.keyWindow?.rootViewController?.present(viewController, animated: true)
|
||||
// guard let presentingViewController = self.presentingViewController else { return false }
|
||||
//
|
||||
// self.navigationController.view.tintColor = .white
|
||||
//
|
||||
// if self.navigationController.viewControllers.isEmpty
|
||||
// {
|
||||
// guard presentingViewController.presentedViewController == nil else { return false }
|
||||
//
|
||||
// self.navigationController.setViewControllers([viewController], animated: false)
|
||||
// presentingViewController.present(self.navigationController, animated: true, completion: nil)
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// viewController.navigationItem.leftBarButtonItem = nil
|
||||
// self.navigationController.pushViewController(viewController, animated: true)
|
||||
// }
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func dismiss() {
|
||||
if let presentingViewController {
|
||||
presentingViewController.dismiss(animated: true)
|
||||
}
|
||||
// UIApplication.shared.keyWindow?.rootViewController?.presentedViewController?.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
private extension AuthenticationOperation
|
||||
@@ -316,29 +326,29 @@ private extension AuthenticationOperation
|
||||
func authenticate()
|
||||
{
|
||||
DispatchQueue.main.async {
|
||||
let authenticationViewController = self.storyboard.instantiateViewController(withIdentifier: "authenticationViewController") as! AuthenticationViewController
|
||||
authenticationViewController.authenticationHandler = { (appleID, password, completionHandler) in
|
||||
self.authenticate(appleID: appleID, password: password) { (result) in
|
||||
completionHandler(result)
|
||||
let viewController = UIHostingController(rootView: NavigationView {
|
||||
ConnectAppleIDView { appleID, password, completionHandler in
|
||||
self.authenticate(appleID: appleID, password: password) { (result) in
|
||||
completionHandler(result)
|
||||
}
|
||||
} completionHandler: { result in
|
||||
if let (account, session, password) = result
|
||||
{
|
||||
// We presented the Auth UI and the user signed in.
|
||||
// In this case, we'll assume we should show the instructions again.
|
||||
self.shouldShowInstructions = true
|
||||
|
||||
self.appleIDPassword = password
|
||||
completionHandler(.success((account, session)))
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(.failure(OperationError.cancelled))
|
||||
}
|
||||
}
|
||||
}
|
||||
authenticationViewController.completionHandler = { (result) in
|
||||
if let (account, session, password) = result
|
||||
{
|
||||
// We presented the Auth UI and the user signed in.
|
||||
// In this case, we'll assume we should show the instructions again.
|
||||
self.shouldShowInstructions = true
|
||||
|
||||
self.appleIDPassword = password
|
||||
completionHandler(.success((account, session)))
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(.failure(OperationError.cancelled))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if !self.present(authenticationViewController)
|
||||
if !self.present(viewController)
|
||||
{
|
||||
completionHandler(.failure(OperationError.notAuthenticated))
|
||||
}
|
||||
@@ -380,8 +390,8 @@ private extension AuthenticationOperation
|
||||
case .success(let anisetteData):
|
||||
let verificationHandler: ((@escaping (String?) -> Void) -> Void)?
|
||||
|
||||
if let presentingViewController = self.presentingViewController
|
||||
{
|
||||
// if let presentingViewController = self.presentingViewController
|
||||
// {
|
||||
verificationHandler = { (completionHandler) in
|
||||
DispatchQueue.main.async {
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""), message: nil, preferredStyle: .alert)
|
||||
@@ -407,22 +417,22 @@ private extension AuthenticationOperation
|
||||
completionHandler(nil)
|
||||
})
|
||||
|
||||
if self.navigationController.presentingViewController != nil
|
||||
{
|
||||
self.navigationController.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
else
|
||||
{
|
||||
presentingViewController.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
// if self.navigationController.presentingViewController != nil
|
||||
// {
|
||||
// self.navigationController.present(alertController, animated: true, completion: nil)
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// presentingViewController.present(alertController, animated: true, completion: nil)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No view controller to present security code alert, so don't provide verificationHandler.
|
||||
verificationHandler = nil
|
||||
}
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // No view controller to present security code alert, so don't provide verificationHandler.
|
||||
// verificationHandler = nil
|
||||
// }
|
||||
|
||||
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData,
|
||||
verificationHandler: verificationHandler) { (account, session, error) in
|
||||
@@ -453,15 +463,15 @@ private extension AuthenticationOperation
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController
|
||||
|
||||
selectTeamViewController.teams = teams
|
||||
selectTeamViewController.completionHandler = completionHandler
|
||||
|
||||
if !self.present(selectTeamViewController)
|
||||
{
|
||||
return completionHandler(.failure(AuthenticationError.noTeam))
|
||||
}
|
||||
// let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController
|
||||
//
|
||||
// selectTeamViewController.teams = teams
|
||||
// selectTeamViewController.completionHandler = completionHandler
|
||||
//
|
||||
// if !self.present(selectTeamViewController)
|
||||
// {
|
||||
// return completionHandler(.failure(AuthenticationError.noTeam))
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -594,7 +604,7 @@ private extension AuthenticationOperation
|
||||
|
||||
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
|
||||
{
|
||||
guard let udid = fetch_udid()?.toString() else {
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else {
|
||||
return completionHandler(.failure(OperationError.unknownUDID))
|
||||
}
|
||||
|
||||
@@ -643,20 +653,21 @@ private extension AuthenticationOperation
|
||||
|
||||
func showInstructionsIfNecessary(completionHandler: @escaping (Bool) -> Void)
|
||||
{
|
||||
guard self.shouldShowInstructions else { return completionHandler(false) }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController
|
||||
instructionsViewController.showsBottomButton = true
|
||||
instructionsViewController.completionHandler = {
|
||||
completionHandler(true)
|
||||
}
|
||||
|
||||
if !self.present(instructionsViewController)
|
||||
{
|
||||
completionHandler(false)
|
||||
}
|
||||
}
|
||||
return completionHandler(false)
|
||||
// guard self.shouldShowInstructions else { return completionHandler(false) }
|
||||
//
|
||||
// DispatchQueue.main.async {
|
||||
// let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController
|
||||
// instructionsViewController.showsBottomButton = true
|
||||
// instructionsViewController.completionHandler = {
|
||||
// completionHandler(true)
|
||||
// }
|
||||
//
|
||||
// if !self.present(instructionsViewController)
|
||||
// {
|
||||
// completionHandler(false)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
func showRefreshScreenIfNecessary(signer: ALTSigner, session: ALTAppleAPISession, completionHandler: @escaping (Bool) -> Void)
|
||||
|
||||
@@ -105,13 +105,8 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
|
||||
} catch {
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
if #available(iOS 17, *) {
|
||||
// TODO: iOS 17 and above have a new JIT implementation that is completely broken in SideStore :(
|
||||
}
|
||||
else {
|
||||
start_auto_mounter(documentsDirectory)
|
||||
}
|
||||
|
||||
start_auto_mounter(documentsDirectory)
|
||||
|
||||
self.managedObjectContext.perform {
|
||||
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))
|
||||
|
||||
|
||||
@@ -29,9 +29,6 @@ class BackupAppOperation: ResultOperation<Void>
|
||||
private var appName: String?
|
||||
private var timeoutTimer: Timer?
|
||||
|
||||
private weak var applicationWillReturnObserver: NSObjectProtocol?
|
||||
private weak var backupResponseObserver: NSObjectProtocol?
|
||||
|
||||
init(action: Action, context: InstallAppOperationContext)
|
||||
{
|
||||
self.action = action
|
||||
@@ -46,7 +43,10 @@ class BackupAppOperation: ResultOperation<Void>
|
||||
|
||||
do
|
||||
{
|
||||
if let error = self.context.error { throw error }
|
||||
if let error = self.context.error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let installedApp = self.context.installedApp, let context = installedApp.managedObjectContext else { throw OperationError.invalidParameters }
|
||||
context.perform {
|
||||
@@ -55,8 +55,9 @@ class BackupAppOperation: ResultOperation<Void>
|
||||
let appName = installedApp.name
|
||||
self.appName = appName
|
||||
|
||||
let altstoreOpenURL = URL(string: "sidestore://")!
|
||||
|
||||
guard let altstoreApp = InstalledApp.fetchAltStore(in: context) else { throw OperationError.appNotFound }
|
||||
let altstoreOpenURL = altstoreApp.openAppURL
|
||||
|
||||
var returnURLComponents = URLComponents(url: altstoreOpenURL, resolvingAgainstBaseURL: false)
|
||||
returnURLComponents?.host = "appBackupResponse"
|
||||
guard let returnURL = returnURLComponents?.url else { throw OperationError.openAppFailed(name: appName) }
|
||||
@@ -152,11 +153,8 @@ private extension BackupAppOperation
|
||||
{
|
||||
func registerObservers()
|
||||
{
|
||||
self.applicationWillReturnObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [weak self] (notification) in
|
||||
defer {
|
||||
self?.applicationWillReturnObserver.map { NotificationCenter.default.removeObserver($0) }
|
||||
}
|
||||
|
||||
var applicationWillReturnObserver: NSObjectProtocol!
|
||||
applicationWillReturnObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [weak self] (notification) in
|
||||
guard let self = self, !self.isFinished else { return }
|
||||
|
||||
self.timeoutTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { [weak self] (timer) in
|
||||
@@ -168,17 +166,18 @@ private extension BackupAppOperation
|
||||
self.finish(.failure(OperationError.timedOut))
|
||||
}
|
||||
}
|
||||
|
||||
NotificationCenter.default.removeObserver(applicationWillReturnObserver!)
|
||||
}
|
||||
|
||||
self.backupResponseObserver = NotificationCenter.default.addObserver(forName: AppDelegate.appBackupDidFinish, object: nil, queue: nil) { [weak self] (notification) in
|
||||
defer {
|
||||
self?.backupResponseObserver.map { NotificationCenter.default.removeObserver($0) }
|
||||
}
|
||||
|
||||
var backupResponseObserver: NSObjectProtocol!
|
||||
backupResponseObserver = NotificationCenter.default.addObserver(forName: AppDelegate.appBackupDidFinish, object: nil, queue: nil) { [weak self] (notification) in
|
||||
self?.timeoutTimer?.invalidate()
|
||||
|
||||
let result = notification.userInfo?[AppDelegate.appBackupResultKey] as? Result<Void, Error> ?? .failure(OperationError.unknownResult)
|
||||
self?.finish(result)
|
||||
|
||||
NotificationCenter.default.removeObserver(backupResponseObserver!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
//
|
||||
// ClearAppCacheOperation.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 9/27/22.
|
||||
// Copyright © 2022 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AltStoreCore
|
||||
/*
|
||||
struct BatchError: ALTLocalizedError
|
||||
{
|
||||
|
||||
enum Code: Int, ALTErrorCode
|
||||
{
|
||||
typealias Error = BatchError
|
||||
|
||||
case batchError
|
||||
}
|
||||
|
||||
var code: Code = .batchError
|
||||
var underlyingErrors: [Error]
|
||||
|
||||
var errorTitle: String?
|
||||
var errorFailure: String?
|
||||
|
||||
init(errors: [Error])
|
||||
{
|
||||
self.underlyingErrors = errors
|
||||
}
|
||||
|
||||
var errorFailureReason: String {
|
||||
guard !self.underlyingErrors.isEmpty else { return NSLocalizedString("An unknown error occured.", comment: "") }
|
||||
|
||||
let errorMessages = self.underlyingErrors.map { $0.localizedDescription }
|
||||
|
||||
let message = errorMessages.joined(separator: "\n\n")
|
||||
return message
|
||||
}
|
||||
}
|
||||
*/
|
||||
@objc(ClearAppCacheOperation)
|
||||
class ClearAppCacheOperation: ResultOperation<Void>
|
||||
{
|
||||
private let coordinator = NSFileCoordinator()
|
||||
private let coordinatorQueue = OperationQueue()
|
||||
|
||||
override init()
|
||||
{
|
||||
self.coordinatorQueue.name = "AltStore - ClearAppCacheOperation Queue"
|
||||
}
|
||||
|
||||
override func main()
|
||||
{
|
||||
super.main()
|
||||
|
||||
var allErrors = [Error]()
|
||||
|
||||
self.clearTemporaryDirectory { result in
|
||||
switch result
|
||||
{
|
||||
//case .failure(let batchError as BatchError): allErrors.append(contentsOf: batchError.underlyingErrors)
|
||||
case .failure(let error): allErrors.append(error)
|
||||
case .success: break
|
||||
}
|
||||
|
||||
self.removeUninstalledAppBackupDirectories { result in
|
||||
switch result
|
||||
{
|
||||
//case .failure(let batchError as BatchError): allErrors.append(contentsOf: batchError.underlyingErrors)
|
||||
case .failure(let error): allErrors.append(error)
|
||||
case .success: break
|
||||
}
|
||||
|
||||
if allErrors.isEmpty
|
||||
{
|
||||
self.finish(.success(()))
|
||||
}
|
||||
else
|
||||
{
|
||||
self.finish(.failure(OperationError.cacheClearError(errors: allErrors.map({ error in
|
||||
return error.localizedDescription
|
||||
}))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension ClearAppCacheOperation
|
||||
{
|
||||
func clearTemporaryDirectory(completion: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
let intent = NSFileAccessIntent.writingIntent(with: FileManager.default.temporaryDirectory, options: [.forDeleting])
|
||||
self.coordinator.coordinate(with: [intent], queue: self.coordinatorQueue) { (error) in
|
||||
do
|
||||
{
|
||||
if let error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
let fileURLs = try FileManager.default.contentsOfDirectory(at: intent.url,
|
||||
includingPropertiesForKeys: [],
|
||||
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
|
||||
var errors = [Error]()
|
||||
|
||||
for fileURL in fileURLs
|
||||
{
|
||||
do
|
||||
{
|
||||
print("[ALTLog] Removing item from temporary directory:", fileURL.lastPathComponent)
|
||||
try FileManager.default.removeItem(at: fileURL)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("[ALTLog] Failed to remove \(fileURL.lastPathComponent) from temporary directory.", error)
|
||||
errors.append(error)
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.isEmpty
|
||||
{
|
||||
completion(.failure(OperationError.cacheClearError(errors: errors.map({ error in
|
||||
return error.localizedDescription
|
||||
}))))
|
||||
}
|
||||
else
|
||||
{
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeUninstalledAppBackupDirectories(completion: @escaping (Result<Void, Error>) -> Void)
|
||||
{
|
||||
guard let backupsDirectory = FileManager.default.appBackupsDirectory else { return completion(.failure(OperationError.missingAppGroup)) }
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
|
||||
let installedAppBundleIDs = Set(InstalledApp.all(in: context).map { $0.bundleIdentifier })
|
||||
|
||||
let intent = NSFileAccessIntent.writingIntent(with: backupsDirectory, options: [.forDeleting])
|
||||
self.coordinator.coordinate(with: [intent], queue: self.coordinatorQueue) { (error) in
|
||||
do
|
||||
{
|
||||
if let error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
var isDirectory: ObjCBool = false
|
||||
guard FileManager.default.fileExists(atPath: intent.url.path, isDirectory: &isDirectory), isDirectory.boolValue else {
|
||||
completion(.success(()))
|
||||
return
|
||||
}
|
||||
|
||||
let fileURLs = try FileManager.default.contentsOfDirectory(at: intent.url,
|
||||
includingPropertiesForKeys: [.isDirectoryKey, .nameKey],
|
||||
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
|
||||
var errors = [Error]()
|
||||
|
||||
|
||||
for backupDirectory in fileURLs
|
||||
{
|
||||
do
|
||||
{
|
||||
let resourceValues = try backupDirectory.resourceValues(forKeys: [.isDirectoryKey, .nameKey])
|
||||
guard let isDirectory = resourceValues.isDirectory, let bundleID = resourceValues.name else { continue }
|
||||
|
||||
if isDirectory && !installedAppBundleIDs.contains(bundleID) && !AppManager.shared.isActivelyManagingApp(withBundleID: bundleID)
|
||||
{
|
||||
print("[ALTLog] Removing backup directory for uninstalled app:", bundleID)
|
||||
try FileManager.default.removeItem(at: backupDirectory)
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("[ALTLog] Failed to remove app backup directory:", error)
|
||||
errors.append(error)
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.isEmpty
|
||||
{
|
||||
completion(.failure(OperationError.cacheClearError(errors: errors.map({ error in
|
||||
return error.localizedDescription
|
||||
}))))
|
||||
}
|
||||
else
|
||||
{
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("[ALTLog] Failed to remove app backup directory:", error)
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,11 @@ final class DeactivateAppOperation: ResultOperation<InstalledApp>
|
||||
{
|
||||
super.main()
|
||||
|
||||
if let error = self.context.error { return self.finish(.failure(error)) }
|
||||
if let error = self.context.error
|
||||
{
|
||||
self.finish(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
let installedApp = context.object(with: self.app.objectID) as! InstalledApp
|
||||
@@ -41,14 +45,14 @@ final class DeactivateAppOperation: ResultOperation<InstalledApp>
|
||||
for profile in allIdentifiers {
|
||||
do {
|
||||
try remove_provisioning_profile(profile)
|
||||
self.progress.completedUnitCount += 1
|
||||
installedApp.isActive = false
|
||||
self.finish(.success(installedApp))
|
||||
break
|
||||
} catch {
|
||||
self.finish(.failure(error))
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
installedApp.isActive = false
|
||||
self.finish(.success(installedApp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,19 +45,13 @@ final class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
||||
guard let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
|
||||
installedApp.managedObjectContext?.perform {
|
||||
var retries = 3
|
||||
while (retries > 0){
|
||||
do {
|
||||
try debug_app(installedApp.resignedBundleIdentifier)
|
||||
self.finish(.success(()))
|
||||
retries = 0
|
||||
} catch {
|
||||
retries -= 1
|
||||
if (retries <= 0){
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
do {
|
||||
try debug_app(installedApp.resignedBundleIdentifier)
|
||||
} catch {
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
|
||||
self.finish(.success(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSoc
|
||||
self.socket.connect()
|
||||
}
|
||||
|
||||
func didReceive(event: WebSocketEvent, client: WebSocketClient) {
|
||||
func didReceive(event: WebSocketEvent, client: WebSocket) {
|
||||
switch event {
|
||||
case .text(let string):
|
||||
do {
|
||||
@@ -429,7 +429,7 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSoc
|
||||
}
|
||||
}
|
||||
|
||||
extension WebSocketClient {
|
||||
extension WebSocket {
|
||||
func json(_ dictionary: [String: String]) {
|
||||
let data = try! JSONSerialization.data(withJSONObject: dictionary, options: [])
|
||||
self.write(string: String(data: data, encoding: .utf8)!)
|
||||
|
||||
@@ -262,6 +262,10 @@ extension FetchProvisioningProfilesOperation
|
||||
{
|
||||
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ALTAppleAPIError(.maximumAppIDLimitReached)
|
||||
}
|
||||
}
|
||||
}
|
||||
//App ID name must be ascii. If the name is not ascii, using bundleID instead
|
||||
|
||||
@@ -41,14 +41,12 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
|
||||
guard
|
||||
let certificate = self.context.certificate,
|
||||
let resignedApp = self.context.resignedApp,
|
||||
let provisioningProfiles = self.context.provisioningProfiles
|
||||
let resignedApp = self.context.resignedApp
|
||||
else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
|
||||
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||
backgroundContext.perform {
|
||||
|
||||
|
||||
/* App */
|
||||
let installedApp: InstalledApp
|
||||
|
||||
@@ -118,7 +116,8 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
// Temporary directory and resigned .ipa no longer needed, so delete them now to ensure AltStore doesn't quit before we get the chance to.
|
||||
self.cleanUp()
|
||||
|
||||
if let sideloadedAppsLimit = UserDefaults.standard.activeAppsLimit, provisioningProfiles.contains(where: { $1.isFreeProvisioningProfile == true })
|
||||
var activeProfiles: Set<String>?
|
||||
if let sideloadedAppsLimit = UserDefaults.standard.activeAppsLimit
|
||||
{
|
||||
// When installing these new profiles, AltServer will remove all non-active profiles to ensure we remain under limit.
|
||||
|
||||
@@ -143,14 +142,15 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
installedApp.isActive = false
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
installedApp.isActive = true
|
||||
|
||||
activeProfiles = Set(activeApps.flatMap { (installedApp) -> [String] in
|
||||
let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier }
|
||||
return [installedApp.resignedBundleIdentifier] + appExtensionProfiles
|
||||
})
|
||||
}
|
||||
|
||||
var installing = true
|
||||
if installedApp.storeApp?.bundleIdentifier.range(of: Bundle.Info.appbundleIdentifier) != nil {
|
||||
if installedApp.storeApp?.bundleIdentifier == Bundle.Info.appbundleIdentifier {
|
||||
// Reinstalling ourself will hang until we leave the app, so we need to exit it without force closing
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||
if UIApplication.shared.applicationState != .active {
|
||||
@@ -162,26 +162,30 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
return
|
||||
}
|
||||
print("We are still installing after 3 seconds")
|
||||
|
||||
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||
switch (settings.authorizationStatus) {
|
||||
case .authorized, .ephemeral, .provisional:
|
||||
print("Notifications are enabled")
|
||||
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = "Refreshing..."
|
||||
content.body = "SideStore will automatically move to the homescreen to finish refreshing!"
|
||||
content.body = "To finish refreshing, SideStore must be moved to the background, which it does by opening Safari. Please reopen SideStore after it is done refreshing!"
|
||||
let notification = UNNotificationRequest(identifier: Bundle.Info.appbundleIdentifier + ".FinishRefreshNotification", content: content, trigger: UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false))
|
||||
UNUserNotificationCenter.current().add(notification)
|
||||
|
||||
DispatchQueue.main.async { UIApplication.shared.open(URL(string: "x-web-search://")!) }
|
||||
|
||||
break
|
||||
default:
|
||||
print("Notifications are not enabled")
|
||||
|
||||
let alert = UIAlertController(title: "Finish Refresh", message: "Please reopen SideStore after the process is finished.To finish refreshing, SideStore must be moved to the background. To do this, you can either go to the Home Screen manually or by hitting Continue. Please reopen SideStore after doing this.", preferredStyle: .alert)
|
||||
|
||||
let alert = UIAlertController(title: "Finish Refresh", message: "To finish refreshing, SideStore must be moved to the background. To do this, you can either go to the Home Screen or open Safari by pressing Continue. Please reopen SideStore after doing this.", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default, handler: { _ in
|
||||
print("Going home")
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
print("Opening Safari")
|
||||
DispatchQueue.main.async { UIApplication.shared.open(URL(string: "x-web-search://")!) }
|
||||
}))
|
||||
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first
|
||||
if var topController = keyWindow?.rootViewController {
|
||||
@@ -190,24 +194,27 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
}
|
||||
topController.present(alert, animated: true)
|
||||
} else {
|
||||
print("No key window? Let's just go home")
|
||||
print("No key window? Let's just open Safari")
|
||||
UIApplication.shared.open(URL(string: "x-web-search://")!)
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try install_ipa(installedApp.bundleIdentifier)
|
||||
installing = false
|
||||
installedApp.refreshedDate = Date()
|
||||
self.finish(.success(installedApp))
|
||||
} catch let error {
|
||||
} catch {
|
||||
installing = false
|
||||
self.finish(.failure(error))
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
|
||||
installedApp.refreshedDate = Date()
|
||||
self.finish(.success(installedApp))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,14 +34,10 @@ enum OperationError: LocalizedError
|
||||
case openAppFailed(name: String)
|
||||
case missingAppGroup
|
||||
|
||||
case noWiFi
|
||||
case tooNewError
|
||||
case anisetteV1Error(message: String)
|
||||
case provisioningError(result: String, message: String?)
|
||||
case anisetteV3Error(message: String)
|
||||
|
||||
case cacheClearError(errors: [String])
|
||||
|
||||
var failureReason: String? {
|
||||
switch self {
|
||||
case .unknown: return NSLocalizedString("An unknown error occured.", comment: "")
|
||||
@@ -57,12 +53,9 @@ enum OperationError: LocalizedError
|
||||
case .openAppFailed(let name): return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), name)
|
||||
case .missingAppGroup: return NSLocalizedString("SideStore's shared app group could not be found.", comment: "")
|
||||
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs.", comment: "")
|
||||
case .noWiFi: return NSLocalizedString("You do not appear to be connected to WiFi!\nSideStore will never be able to install or refresh applications without WiFi.", comment: "")
|
||||
case .tooNewError: return NSLocalizedString("iOS 17 has changed how JIT is enabled therefore SideStore cannot enable it at this time, sorry for any inconvenience.\nWe will let everyone know once we have a solution!", comment: "")
|
||||
case .anisetteV1Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: ""), message)
|
||||
case .provisioningError(let result, let message): return String(format: NSLocalizedString("An error occurred when provisioning: %@%@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), result, message != nil ? (" (" + message! + ")") : "")
|
||||
case .anisetteV3Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), message)
|
||||
case .cacheClearError(let errors): return String(format: NSLocalizedString("An error occurred while clearing cache: %@", comment: ""), errors.joined(separator: "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,8 +138,8 @@ extension MinimuxerError: LocalizedError {
|
||||
return self.createService(name: "AFC")
|
||||
case .RwAfc:
|
||||
return NSLocalizedString("AFC was unable to manage files on the device", comment: "")
|
||||
case .InstallApp(let message):
|
||||
return NSLocalizedString("Unable to install the app: \(message.toString())", comment: "")
|
||||
case .InstallApp:
|
||||
return NSLocalizedString("Unable to install the app from the staging directory", comment: "")
|
||||
case .UninstallApp:
|
||||
return NSLocalizedString("Unable to uninstall the app", comment: "")
|
||||
|
||||
|
||||
@@ -35,28 +35,31 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
|
||||
do
|
||||
{
|
||||
if let error = self.context.error { return self.finish(.failure(error)) }
|
||||
if let error = self.context.error
|
||||
{
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let profiles = self.context.provisioningProfiles else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
guard let app = self.context.app else { return self.finish(.failure(OperationError.appNotFound)) }
|
||||
guard let profiles = self.context.provisioningProfiles else { throw OperationError.invalidParameters }
|
||||
|
||||
for p in profiles {
|
||||
do {
|
||||
let bytes = p.value.data.toRustByteSlice()
|
||||
try install_provisioning_profile(bytes.forRust())
|
||||
} catch {
|
||||
self.finish(.failure(MinimuxerError.ProfileInstall))
|
||||
}
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
guard let app = self.context.app else { throw OperationError.appNotFound }
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
print("Sending refresh app request...")
|
||||
|
||||
for p in profiles {
|
||||
do {
|
||||
let bytes = p.value.data.toRustByteSlice()
|
||||
try install_provisioning_profile(bytes.forRust())
|
||||
} catch {
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
let predicate = NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), app.bundleIdentifier)
|
||||
self.managedObjectContext.perform {
|
||||
guard let installedApp = InstalledApp.first(satisfying: predicate, in: self.managedObjectContext) else {
|
||||
self.finish(.failure(OperationError.appNotFound))
|
||||
return
|
||||
}
|
||||
installedApp.update(provisioningProfile: p.value)
|
||||
@@ -69,5 +72,9 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import Roxas
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import minimuxer
|
||||
|
||||
@objc(ResignAppOperation)
|
||||
final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
@@ -116,9 +115,7 @@ private extension ResignAppOperation
|
||||
|
||||
infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
|
||||
infoDictionary[Bundle.Info.altBundleID] = identifier
|
||||
infoDictionary[Bundle.Info.devicePairingString] = "<insert pairing file here>"
|
||||
infoDictionary.removeValue(forKey: "DTXcode")
|
||||
infoDictionary.removeValue(forKey: "DTXcodeBuild")
|
||||
infoDictionary[Bundle.Info.devicePairingString] = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String
|
||||
|
||||
for (key, value) in additionalInfoDictionaryValues
|
||||
{
|
||||
@@ -184,9 +181,9 @@ private extension ResignAppOperation
|
||||
|
||||
if app.isAltStoreApp
|
||||
{
|
||||
guard let udid = fetch_udid()?.toString() as? String else { throw OperationError.unknownUDID }
|
||||
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else { throw OperationError.unknownUDID }
|
||||
guard let pairingFileString = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.devicePairingString) as? String else { throw OperationError.unknownUDID }
|
||||
additionalValues[Bundle.Info.devicePairingString] = "<insert pairing file here>"
|
||||
additionalValues[Bundle.Info.devicePairingString] = pairingFileString
|
||||
additionalValues[Bundle.Info.deviceID] = udid
|
||||
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.preferredServerID
|
||||
|
||||
@@ -205,7 +202,7 @@ private extension ResignAppOperation
|
||||
// The embedded certificate + certificate identifier are already in app bundle, no need to update them.
|
||||
}
|
||||
}
|
||||
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = fetch_udid()?.toString() as? String
|
||||
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String
|
||||
{
|
||||
// There is an ALTDeviceID entry, so assume the app is using AltKit and replace it with the device's UDID.
|
||||
additionalValues[Bundle.Info.deviceID] = udid
|
||||
@@ -230,7 +227,6 @@ private extension ResignAppOperation
|
||||
|
||||
// Prepare app
|
||||
try prepare(appBundle, additionalInfoDictionaryValues: additionalValues)
|
||||
try self.removeMissingAppExtensionReferences(from: appBundle)
|
||||
|
||||
if let directory = appBundle.builtInPlugInsURL, let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants])
|
||||
{
|
||||
@@ -271,28 +267,4 @@ private extension ResignAppOperation
|
||||
|
||||
return progress
|
||||
}
|
||||
|
||||
func removeMissingAppExtensionReferences(from bundle: Bundle) throws
|
||||
{
|
||||
// If app extensions have been removed from an app (either by AltStore or the developer),
|
||||
// we must remove all references to them from SC_Info/Manifest.plist (if it exists).
|
||||
|
||||
let scInfoURL = bundle.bundleURL.appendingPathComponent("SC_Info")
|
||||
let manifestPlistURL = scInfoURL.appendingPathComponent("Manifest.plist")
|
||||
|
||||
guard let manifestPlist = NSMutableDictionary(contentsOf: manifestPlistURL), let sinfReplicationPaths = manifestPlist["SinfReplicationPaths"] as? [String] else { return }
|
||||
|
||||
// Remove references to missing files.
|
||||
let filteredReplicationPaths = sinfReplicationPaths.filter { path in
|
||||
guard let fileURL = URL(string: path, relativeTo: bundle.bundleURL) else { return false }
|
||||
|
||||
let fileExists = FileManager.default.fileExists(atPath: fileURL.path)
|
||||
return fileExists
|
||||
}
|
||||
|
||||
manifestPlist["SinfReplicationPaths"] = filteredReplicationPaths
|
||||
|
||||
// Save updated Manifest.plist to disk.
|
||||
try manifestPlist.write(to: manifestPlistURL)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ final class SendAppOperation: ResultOperation<()>
|
||||
|
||||
if let error = self.context.error
|
||||
{
|
||||
return self.finish(.failure(error))
|
||||
self.finish(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
guard let resignedApp = self.context.resignedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
|
||||
@@ -48,16 +49,15 @@ final class SendAppOperation: ResultOperation<()>
|
||||
do {
|
||||
let bytes = Data(data).toRustByteSlice()
|
||||
try yeet_app_afc(app.bundleIdentifier, bytes.forRust())
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
} catch {
|
||||
self.finish(.failure(MinimuxerError.RwAfc))
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
} else {
|
||||
print("IPA doesn't exist????")
|
||||
self.finish(.failure(OperationError.appNotFound))
|
||||
self.finish(.failure(ALTServerError(.underlyingError)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
AltStore/Protocols/Filterable.swift
Normal file
23
AltStore/Protocols/Filterable.swift
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Filterable.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 01.12.22.
|
||||
// Copyright © 2022 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol Filterable {
|
||||
func matches(_ searchText: String) -> Bool
|
||||
}
|
||||
|
||||
extension Collection where Element: Filterable {
|
||||
func matches(_ searchText: String) -> Bool {
|
||||
self.contains(where: { $0.matches(searchText) })
|
||||
}
|
||||
|
||||
func items(matching searchText: String) -> [Element] {
|
||||
self.filter { $0.matches(searchText) }
|
||||
}
|
||||
}
|
||||
22
AltStore/Protocols/NavigationTab.swift
Normal file
22
AltStore/Protocols/NavigationTab.swift
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// NavigationTab.swift
|
||||
// SideStoreUI
|
||||
//
|
||||
// Created by Fabian Thies on 18.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SFSafeSymbols
|
||||
|
||||
protocol NavigationTab: RawRepresentable, Identifiable, CaseIterable, Hashable where RawValue == Int {
|
||||
static var defaultTab: Self { get }
|
||||
var displaySymbol: SFSymbol { get }
|
||||
var displayName: String { get }
|
||||
}
|
||||
|
||||
extension NavigationTab {
|
||||
var id: Int {
|
||||
self.rawValue
|
||||
}
|
||||
}
|
||||
11
AltStore/Protocols/ViewModel.swift
Normal file
11
AltStore/Protocols/ViewModel.swift
Normal file
@@ -0,0 +1,11 @@
|
||||
//
|
||||
// ViewModel.swift
|
||||
// SideStoreUI
|
||||
//
|
||||
// Created by Fabian Thies on 18.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
protocol ViewModel: ObservableObject {}
|
||||
Binary file not shown.
@@ -1,158 +1 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "40.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "60.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "29.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "58.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "87.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "80.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "120.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "57.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "1x",
|
||||
"size" : "57x57"
|
||||
},
|
||||
{
|
||||
"filename" : "114.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "57x57"
|
||||
},
|
||||
{
|
||||
"filename" : "120.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "180.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "20.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "40.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "29.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "58.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "40.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "80.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "50.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "50x50"
|
||||
},
|
||||
{
|
||||
"filename" : "100.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "50x50"
|
||||
},
|
||||
{
|
||||
"filename" : "72.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "72x72"
|
||||
},
|
||||
{
|
||||
"filename" : "144.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "72x72"
|
||||
},
|
||||
{
|
||||
"filename" : "76.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "152.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "167.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xFA",
|
||||
"green" : "0x05",
|
||||
"red" : "0xA4"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
12
AltStore/Resources/Assets.xcassets/Riley.imageset/Contents.json
vendored
Normal file
12
AltStore/Resources/Assets.xcassets/Riley.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "riley.jpg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
AltStore/Resources/Assets.xcassets/Riley.imageset/riley.jpg
vendored
Normal file
BIN
AltStore/Resources/Assets.xcassets/Riley.imageset/riley.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
12
AltStore/Resources/Assets.xcassets/Shane.imageset/Contents.json
vendored
Normal file
12
AltStore/Resources/Assets.xcassets/Shane.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "shane.jpeg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
AltStore/Resources/Assets.xcassets/Shane.imageset/shane.jpeg
vendored
Normal file
BIN
AltStore/Resources/Assets.xcassets/Shane.imageset/shane.jpeg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 846 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "1024.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
166
AltStore/Resources/en.lproj/Localizable.strings
Normal file
166
AltStore/Resources/en.lproj/Localizable.strings
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
AltStore
|
||||
|
||||
Created by Fabian Thies on 22.12.22.
|
||||
Copyright © 2022 SideStore. All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
/* General Actions */
|
||||
"Action.done" = "Done";
|
||||
"Action.close" = "Close";
|
||||
|
||||
|
||||
/* NewsView */
|
||||
"NewsView.title" = "News";
|
||||
"NewsView.Section.FromSources.title" = "From your Sources";
|
||||
|
||||
|
||||
/* BrowseView */
|
||||
"BrowseView.title" = "Browse";
|
||||
"BrowseView.search" = "Search";
|
||||
"BrowseView.Section.PromotedCategories.title" = "Promoted Categories";
|
||||
"BrowseView.Section.PromotedCategories.showAll" = "Show all";
|
||||
"BrowseView.Section.AllApps.title" = "All Apps";
|
||||
"BrowseView.Hints.NoApps.title" = "You don't have any apps yet.";
|
||||
"BrowseView.Hints.NoApps.text" = "Apps are provided by \"sources\". The specification for them is an open standard, so everyone can create their own source. To get you started, we have compiled a list of \"Trusted Sources\" which you can check out by tapping the button below.";
|
||||
"BrowseView.Hints.NoApps.addSource" = "Add Source";
|
||||
"BrowseView.Actions.sources" = "Sources";
|
||||
"BrowseView.Categories.gamesAndEmulators" = "Games and\nEmulators";
|
||||
|
||||
/* AppRowView */
|
||||
"AppRowView.sideloaded" = "Sideloaded";
|
||||
|
||||
/* AppPillButton */
|
||||
"AppPillButton.free" = "Free";
|
||||
"AppPillButton.open" = "Open";
|
||||
|
||||
/* RootView */
|
||||
"RootView.news" = "News";
|
||||
"RootView.browse" = "Browse";
|
||||
"RootView.myApps" = "My Apps";
|
||||
"RootView.settings" = "Settings";
|
||||
|
||||
/* SettingsView */
|
||||
"SettingsView.ConnectedAppleID.name" = "Name";
|
||||
"SettingsView.ConnectedAppleID.eMail" = "E-Mail";
|
||||
"SettingsView.ConnectedAppleID.type" = "Type";
|
||||
"SettingsView.ConnectedAppleID.text" = "Connected Apple ID";
|
||||
"SettingsView.ConnectedAppleID.signOut" = "Sign Out";
|
||||
"SettingsView.ConnectedAppleID.Footer.p1" = "Your Apple ID is required to sign the apps you install with SideStore.";
|
||||
"SettingsView.ConnectedAppleID.Footer.p2" = "Your credentials are only sent to Apple's servers and are not accessible by the SideStore Team. Once successfully logged in, the login details are stored securely on your device.";
|
||||
|
||||
"SettingsView.connectAppleID" = "Connect your Apple ID";
|
||||
"SettingsView.backgroundRefresh" = "Background Refresh";
|
||||
"SettingsView.addToSiri" = "Add to Siri...";
|
||||
"SettingsView.refreshingApps" = "Refreshing Apps";
|
||||
"SettingsView.switchToUIKit" = "Switch to UIKit";
|
||||
"SettingsView.resetImageCache" = "Reset Image Cache";
|
||||
"SettingsView.debug" = "Debug";
|
||||
"SettingsView.swiftUIRedesign" = "SwiftUI Redesign";
|
||||
"SettingsView.credits" = "Credits";
|
||||
"SettingsView.title" = "Settings";
|
||||
"SettingsView.refreshingAppsFooter" = "Enable Background Refresh to automatically refresh apps in the background when connected to WiFi and with Wireguard active.";
|
||||
|
||||
/* ConnectAppleIDView */
|
||||
"ConnectAppleIDView.startWithSignIn" = "Sign in with your Apple ID to get started.";
|
||||
"ConnectAppleIDView.signIn" = "Sign In";
|
||||
"ConnectAppleIDView.appleID" = "Apple ID";
|
||||
"ConnectAppleIDView.password" = "Password";
|
||||
"ConnectAppleIDView.whyDoWeNeedThis" = "Why do we need this?";
|
||||
"ConnectAppleIDView.footer" = "Your Apple ID is used to configure apps so they can be installed on this device. Your credentials will be stored securely in this device's Keychain and sent only to Apple for authentication.";
|
||||
"ConnectAppleIDView.connectYourAppleID" = "Connect Your Apple ID";
|
||||
"ConnectAppleIDView.cancel" = "Cancel";
|
||||
"ConnectAppleIDView.failedToSignIn" = "Failed to Sign In";
|
||||
|
||||
/* SourcesView */
|
||||
"SourcesView.sourcesDescription" = "Sources control what apps are available to download through SideStore.";
|
||||
"SourcesView.remove" = "Remove";
|
||||
"SourcesView.trustedSources" = "Trusted Sources";
|
||||
"SourcesView.reviewedText" = "SideStore has reviewed these sources to make sure they meet our safety standards.";
|
||||
"SourcesView.sources" = "Sources";
|
||||
"SourcesView.done" = "Done";
|
||||
|
||||
/* AddSourceView */
|
||||
"AddSourceView.sourceURL" = "Source URL";
|
||||
"AddSourceView.sourceWarning" = "Please enter the source url here. Then, tap continue to validate and add the source in the next step.";
|
||||
"AddSourceView.sourceWarningContinued" = "Be careful with unvalidated third-party sources! Make sure to only add sources that you trust.";
|
||||
"AddSourceView.continue" = "Continue";
|
||||
"AddSourceView.title" = "Add Source";
|
||||
|
||||
/* ConfirmAddSourceView */
|
||||
"ConfirmAddSourceView.apps" = "Apps";
|
||||
"ConfirmAddSourceView.newsItems" = "News Items";
|
||||
"ConfirmAddSourceView.sourceContents" = "Source Contents";
|
||||
"ConfirmAddSourceView.sourceIdentifier" = "Source Identifier";
|
||||
"ConfirmAddSourceView.sourceURL" = "Source URL";
|
||||
"ConfirmAddSourceView.sourceInfo" = "Source Information";
|
||||
"ConfirmAddSourceView.addSource" = "Add Source";
|
||||
|
||||
/* AppPillButton */
|
||||
"AppPillButton.free" = "Free";
|
||||
"AppPillButton.open" = "Open";
|
||||
|
||||
/* AppAction */
|
||||
"AppAction.install" = "Install";
|
||||
"AppAction.open" = "Open";
|
||||
"AppAction.refresh" = "Refresh";
|
||||
"AppAction.activate" = "Activate";
|
||||
"AppAction.deactivate" = "Deactivate";
|
||||
"AppAction.remove" = "Remove";
|
||||
"AppAction.enableJIT" = "Activate JIT";
|
||||
"AppAction.backup" = "Backup";
|
||||
"AppAction.exportBackup" = "Export backup";
|
||||
"AppAction.restoreBackup" = "Restore backup";
|
||||
"AppAction.chooseCustomIcon" = "Customize icon";
|
||||
"AppAction.resetIcon" = "Reset icon";
|
||||
|
||||
/* AppDetailView*/
|
||||
"AppDetailView.Badge.official" = "Official App";
|
||||
"AppDetailView.Badge.trusted" = "From Trusted Source";
|
||||
"AppDetailView.noScreenshots" = "No screenshots available for this app.";
|
||||
"AppDetailView.more" = "More...";
|
||||
"AppDetailView.whatsNew" = "What's New";
|
||||
"AppDetailView.WhatsNew.versionHistory" = "Version History";
|
||||
"AppDetailView.WhatsNew.showOnGithub" = "Show project on GitHub";
|
||||
"AppDetailView.reviews" = "Ratings & Reviews";
|
||||
"AppDetailView.Reviews.outOf" = "out of %d";
|
||||
"AppDetailView.Reviews.ratings" = "%d Ratings";
|
||||
"AppDetailView.Reviews.seeAll" = "See All";
|
||||
"AppDetailView.version" = "Version %@";
|
||||
"AppDetailView.noVersionInformation" = "No version information";
|
||||
"AppDetailView.noPermissions" = "The app requires no permissions.";
|
||||
"AppDetailView.permissions" = "Permissions";
|
||||
"AppDetailView.information" = "Information";
|
||||
"AppDetailView.Information.source" = "Source";
|
||||
"AppDetailView.Information.developer" = "Developer";
|
||||
"AppDetailView.Information.size" = "Size";
|
||||
"AppDetailView.Information.latestVersion" = "Latest Version";
|
||||
"AppDetailView.Information.compatibility" = "Compatibility";
|
||||
"AppDetailView.Information.compatibilityCompatible" = "Unknown";
|
||||
"AppDetailView.Information.compatibilityUnknown" = "Unknown";
|
||||
"AppDetailView.Information.compatibilityAtLeast" = "Requires iOS %@ or higher";
|
||||
"AppDetailView.Information.compatibilityOrLower" = "Requires iOS %@ or lower";
|
||||
|
||||
/* AppPermissionGrid */
|
||||
"AppPermissionGrid.usageDescription" = "Usage Description";
|
||||
|
||||
/* MyAppsView */
|
||||
"MyAppsView.active" = "Active";
|
||||
"MyAppsView.sideloading" = "Sideloading in progress...";
|
||||
"MyAppsView.refreshAll" = "Refresh All";
|
||||
"MyAppsView.appIDsRemaining" = "App IDs Remaining";
|
||||
"MyAppsView.failedToRefresh" = "Failed to refresh";
|
||||
"MyAppsView.apps" = "apps"; /* Keep this lowercase */
|
||||
"MyAppsView.viewAppIDs" = "View App IDs";
|
||||
"MyAppsView.myApps" = "My Apps";
|
||||
"MyAppsView.Hints.NoUpdates.title" = "All Apps are Up To Date";
|
||||
"MyAppsView.Hints.NoUpdates.text" = "You will be notified once updates for your apps are available. The updates will then be shown here.";
|
||||
"MyAppsView.Hints.NoUpdates.dismissForNow" = "Dismiss for now";
|
||||
"MyAppsView.Hints.NoUpdates.dontShowAgain" = "Don't show this again";
|
||||
|
||||
|
||||
/* AppIDsView */
|
||||
"AppIDsView.title" = "App IDs";
|
||||
"AppIDsView.description" = "Each app and app extension installed with SideStore must register an App ID with Apple.\n\nApp IDs for paid developer accounts never expire, and there is no limit to how many you can create.";
|
||||
124
AltStore/Resources/es-419.lproj/Localizable.strings
Normal file
124
AltStore/Resources/es-419.lproj/Localizable.strings
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
AltStore
|
||||
|
||||
Created by Joshua Laymon on 23.12.22.
|
||||
Updated by Gabriel Morazán on 25.12.22.
|
||||
Copyright © 2022 SideStore. All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
/* NewsView */
|
||||
"NewsView.title" = "Noticias";
|
||||
"NewsView.Section.FromSources.title" = "De tus fuentes";
|
||||
|
||||
|
||||
/* BrowseView */
|
||||
"BrowseView.title" = "Navegar";
|
||||
"BrowseView.search" = "Buscar";
|
||||
"BrowseView.Section.PromotedCategories.title" = "Categorías promocionadas";
|
||||
"BrowseView.Section.PromotedCategories.showAll" = "Mostrar todo";
|
||||
"BrowseView.Section.AllApps.title" = "Todas las aplicaciones";
|
||||
"BrowseView.Actions.sources" = "Fuentes";
|
||||
"BrowseView.Categories.gamesAndEmulators" = "Juegos y\nemuladores";
|
||||
|
||||
/* RootView */
|
||||
"RootView.news" = "Noticias";
|
||||
"RootView.browse" = "Navegar";
|
||||
"RootView.myApps" = "Mis apps";
|
||||
"RootView.settings" = "Configuración";
|
||||
|
||||
/* SettingsView */
|
||||
"SettingsView.ConnectedAppleID.name" = "Nombre";
|
||||
"SettingsView.ConnectedAppleID.eMail" = "Correo electrónico";
|
||||
"SettingsView.ConnectedAppleID.type" = "Tipo";
|
||||
"SettingsView.ConnectedAppleID.text" = "Apple ID conectado";
|
||||
"SettingsView.ConnectedAppleID.signOut" = "Cerrar sesión";
|
||||
"SettingsView.ConnectedAppleID.Footer.p1" = "Se requiere de su Apple ID para firmar las aplicaciones que se instalan con SideStore.";
|
||||
"SettingsView.ConnectedAppleID.Footer.p2" = "Sus datos son enviados solamente a Apple, SideStore Team no tiene acceso alguno a sus credenciales. Los detalles del inicio de sesión serán guardadas de forma segura en su dispositivo después de haber iniciado sesión exitosamente.";
|
||||
"SettingsView.connectAppleID" = "Inicia sesión con un Apple ID";
|
||||
"SettingsView.backgroundRefresh" = "Refrescar en segundo plano";
|
||||
"SettingsView.addToSiri" = "Agregar a Siri...";
|
||||
"SettingsView.refreshingApps" = "Refrescando las aplicaciones";
|
||||
"SettingsView.switchToUIKit" = "Cambiar a UIKit";
|
||||
"SettingsView.resetImageCache" = "Borrar el caché de imagenes";
|
||||
"SettingsView.debug" = "Depurar";
|
||||
"SettingsView.swiftUIRedesign" = "Rediseño de SwiftUI";
|
||||
"SettingsView.credits" = "Créditos";
|
||||
"SettingsView.title" = "Configuración";
|
||||
"SettingsView.refreshingAppsFooter" = "Active el refresco en segundo plano para que las aplicaciones sean refrescadas automáticamente cuando estés conectado a una red Wi-Fi y tengas a WireGuard activado.";
|
||||
|
||||
/* ConnectAppleIDView */
|
||||
"ConnectAppleIDView.appleID" = "Apple ID";
|
||||
"ConnectAppleIDView.startWithSignIn" = "Inicie sesión con su Apple ID para comenzar.";
|
||||
"ConnectAppleIDView.signIn" = "Iniciar sesión";
|
||||
"ConnectAppleIDView.password" = "Contraseña";
|
||||
"ConnectAppleIDView.whyDoWeNeedThis" = "¿Por qué necesitamos esto?";
|
||||
"ConnectAppleIDView.footer" = "Su Apple ID es usado para configurar las aplicaciones que se instalarán en su dispositivo. Sus credenciales serán guardadas de forma segura en su dispositivo y seran enviados solamente a Apple para ser autenticados.";
|
||||
"ConnectAppleIDView.connectYourAppleID" = "Conectar su Apple ID";
|
||||
"ConnectAppleIDView.cancel" = "Cancelar";
|
||||
"ConnectAppleIDView.failedToSignIn" = "No se pudo iniciar sesión";
|
||||
|
||||
/* SourcesView */
|
||||
"SourcesView.sourcesDescription" = "Las fuentes determinan que aplicaciones estarán disponibles a través del SideStore.";
|
||||
"SourcesView.remove" = "Eliminar";
|
||||
"SourcesView.trustedSources" = "Fuentes confiables";
|
||||
"SourcesView.reviewedText" = "SideStore Team ha verificado que estas fuentes son seguras de usar.";
|
||||
"SourcesView.sources" = "Fuentes";
|
||||
"SourcesView.done" = "Listo";
|
||||
|
||||
/* AddSourceView */
|
||||
"AddSourceView.sourceURL" = "URL de fuente";
|
||||
"AddSourceView.sourceWarning" = "Introduce el URL de una fuente aquí. Luego, toca \"Continuar\" para validar y agregar la fuente en el siguiente paso.";
|
||||
"AddSourceView.sourceWarningContinued" = "¡Ten cuidado con las fuentes de terceros! Asegurate de solo agregar las fuentes en las que confíes.";
|
||||
"AddSourceView.continue" = "Continuar";
|
||||
"AddSourceView.title" = "Agregar fuente";
|
||||
|
||||
/* ConfirmAddSourceView */
|
||||
"ConfirmAddSourceView.apps" = "Aplicaciones";
|
||||
"ConfirmAddSourceView.newsItems" = "Noticias";
|
||||
"ConfirmAddSourceView.sourceContents" = "Contenidos de la fuente";
|
||||
"ConfirmAddSourceView.sourceIdentifier" = "Identificador de la fuente";
|
||||
"ConfirmAddSourceView.sourceURL" = "URL de la fuente";
|
||||
"ConfirmAddSourceView.sourceInfo" = "Información sobre la fuente";
|
||||
"ConfirmAddSourceView.addSource" = "Agregar una fuente";
|
||||
|
||||
/* AppPillButton */
|
||||
"AppPillButton.free" = "Gratis";
|
||||
"AppPillButton.open" = "Abrir";
|
||||
|
||||
/* AppAction */
|
||||
"AppAction.install" = "Instalar";
|
||||
"AppAction.open" = "Abrir";
|
||||
"AppAction.refresh" = "Refrescar";
|
||||
"AppAction.activate" = "Activar";
|
||||
"AppAction.deactivate" = "Desactivar";
|
||||
"AppAction.remove" = "Eliminar";
|
||||
"AppAction.enableJIT" = "Activar JIT";
|
||||
"AppAction.backup" = "Respaldar";
|
||||
"AppAction.exportBackup" = "Exportar respaldo";
|
||||
"AppAction.restoreBackup" = "Restablecer respaldo";
|
||||
"AppAction.chooseCustomIcon" = "Personalizar icono";
|
||||
"AppAction.resetIcon" = "Reiniciar icono";
|
||||
|
||||
/* AppDetailView*/
|
||||
"AppDetailView.more" = "Más...";
|
||||
"AppDetailView.whatsNew" = "Novedades";
|
||||
"AppDetailView.version" = "Versión";
|
||||
"AppDetailView.noVersionInformation" = "Sin información de la versión";
|
||||
"AppDetailView.noPermissions" = "Esta aplicación no requiere de permisos.";
|
||||
"AppDetailView.permissions" = "Los permisos";
|
||||
|
||||
/* AppPermissionGrid */
|
||||
"AppPermissionGrid.usageDescription" = "Descripciones de uso";
|
||||
|
||||
/* MyAppsView */
|
||||
"MyAppsView.active" = "Activo";
|
||||
"MyAppsView.sideloading" = "Instalación en progreso...";
|
||||
"MyAppsView.refreshAll" = "Refrescar todo";
|
||||
"MyAppsView.appIDsRemaining" = "IDs de aplicaciones restantes";
|
||||
"MyAppsView.noUpdatesAvailable" = "No hay actualizaciones disponibles";
|
||||
"MyAppsView.failedToRefresh" = "No se pudo refrescar";
|
||||
"MyAppsView.apps" = "aplicaciones"; /* Keep this lowercase */
|
||||
"MyAppsView.viewAppIDs" = "Ver IDs de las aplicaciones";
|
||||
"MyAppsView.myApps" = "Mis aplicaciones";
|
||||
112
AltStore/Resources/fr.lproj/Localizable.strings
Normal file
112
AltStore/Resources/fr.lproj/Localizable.strings
Normal file
@@ -0,0 +1,112 @@
|
||||
|
||||
|
||||
|
||||
/* NewsView */
|
||||
"NewsView.title" = "Actualités";
|
||||
"BrowseView.search" = "Rechercher";
|
||||
"BrowseView.Actions.sources" = "Sources";
|
||||
"AppAction.chooseCustomIcon" = "Personaliser l'icone";
|
||||
|
||||
/* AppPermissionGrid */
|
||||
"AppPermissionGrid.usageDescription" = "Description d'usage";
|
||||
"MyAppsView.noUpdatesAvailable" = "Aucunes mises à jour disponibles";
|
||||
|
||||
|
||||
/* BrowseView */
|
||||
"BrowseView.title" = "Parcourir";
|
||||
"BrowseView.Section.PromotedCategories.showAll" = "Montrer tout";
|
||||
"BrowseView.Section.AllApps.title" = "Toutes les applications";
|
||||
"BrowseView.Section.PromotedCategories.title" = "Catégories promus";
|
||||
"ConnectAppleIDView.whyDoWeNeedThis" = "Pourquoi avons-nous besoin de cela ?";
|
||||
|
||||
/* RootView */
|
||||
"RootView.news" = "Actualités";
|
||||
"RootView.browse" = "Parcourir";
|
||||
"RootView.myApps" = "Mes applications";
|
||||
"RootView.settings" = "Réglages";
|
||||
|
||||
/* SettingsView */
|
||||
"SettingsView.ConnectedAppleID.name" = "Nom";
|
||||
"SettingsView.ConnectedAppleID.eMail" = "Adresse courriel";
|
||||
"SettingsView.ConnectedAppleID.type" = "Type";
|
||||
"SettingsView.ConnectedAppleID.text" = "Identifiant Apple connecté";
|
||||
"SettingsView.ConnectedAppleID.signOut" = "Déconnexion";
|
||||
"SettingsView.connectAppleID" = "Connecter votre identifiant Apple";
|
||||
"SettingsView.backgroundRefresh" = "Rafraîchissement en arrière plan";
|
||||
"SettingsView.switchToUIKit" = "Changer vers UIKit";
|
||||
"SettingsView.addToSiri" = "Ajouter à Siri...";
|
||||
"SettingsView.debug" = "Déboguer";
|
||||
"SettingsView.swiftUIRedesign" = "Refonte SwiftUI";
|
||||
"SettingsView.credits" = "Crédits";
|
||||
"SettingsView.title" = "Réglages";
|
||||
"SettingsView.refreshingAppsFooter" = "Active la rafraîchissement dans l'arrière plan pour que SideStore soit capable de rafraîchir vos applications dans l'arrière plan lorsque vous êtes connecté au Wi-Fi avec Wireguard activé.";
|
||||
|
||||
/* ConnectAppleIDView */
|
||||
"ConnectAppleIDView.startWithSignIn" = "Connectez-vous avec votre identifiant Apple pour commencer.";
|
||||
"ConnectAppleIDView.signIn" = "Connection";
|
||||
"ConnectAppleIDView.appleID" = "Identifiant Apple";
|
||||
"ConnectAppleIDView.password" = "Mot de passe";
|
||||
"ConnectAppleIDView.connectYourAppleID" = "Connecter votre identifiant Apple";
|
||||
"ConnectAppleIDView.cancel" = "Annuler";
|
||||
"ConnectAppleIDView.failedToSignIn" = "Tentative de connexion échouée";
|
||||
|
||||
/* SourcesView */
|
||||
"SourcesView.sourcesDescription" = "Les sources contiennent des applications qui peuvent être installés avec SideStore.";
|
||||
"SourcesView.remove" = "Enlever";
|
||||
"SourcesView.trustedSources" = "Sources Fiables";
|
||||
"SourcesView.reviewedText" = "L'équipe de SideStore a inspecté ces sources pour vérifier s'ils répondes à nos normes de sécurité.";
|
||||
"SourcesView.sources" = "Sources";
|
||||
"SourcesView.done" = "OK";
|
||||
|
||||
/* AddSourceView */
|
||||
"AddSourceView.sourceURL" = "URL du Source";
|
||||
"AddSourceView.continue" = "Continuer";
|
||||
"AddSourceView.title" = "Ajouter une source";
|
||||
"AppAction.exportBackup" = "Exporter la sauvegarde";
|
||||
"AppAction.restoreBackup" = "Restaurer la sauvegarde";
|
||||
"AppAction.resetIcon" = "Réinitialiser l'icone";
|
||||
|
||||
/* AppDetailView*/
|
||||
"AppDetailView.more" = "Plus...";
|
||||
"AppDetailView.whatsNew" = "Quoi de neuf";
|
||||
"AppDetailView.version" = "Version";
|
||||
"AppDetailView.noPermissions" = "Cette application nécessite aucune permissions.";
|
||||
"AppDetailView.permissions" = "Permissions";
|
||||
|
||||
/* MyAppsView */
|
||||
"MyAppsView.active" = "Active";
|
||||
"MyAppsView.sideloading" = "Installation en cours...";
|
||||
"MyAppsView.refreshAll" = "Rafraîchir tout";
|
||||
"NewsView.Section.FromSources.title" = "De vos Sources";
|
||||
"ConnectAppleIDView.footer" = "Votre identifiant Apple est utilisé pour configurer les applications pour qu'il peuvent être installé sur votre appareil. Les informations de votre compte vont être gardés en sécurité sur votre appareil et sont envoyés uniquement à Apple pour t'authentiquer.";
|
||||
"AppDetailView.noVersionInformation" = "Aucune information sur la version";
|
||||
"SettingsView.ConnectedAppleID.Footer.p1" = "Votre identifiant Apple est requis pour signer les applications que vous installez avec SideStore.";
|
||||
"SettingsView.ConnectedAppleID.Footer.p2" = "Vos informations sont envoyés uniquement aux servers d'Apple et ne sont pas accessibles par l'équipe de SideStore. Les informations de votre compte vont être gardés uniquement sur votre appareil.";
|
||||
"AddSourceView.sourceWarning" = "Entrez l'URL du source ici. En suite, appuyez \"Continuer\" pour valider le source et l'ajouter.";
|
||||
"AddSourceView.sourceWarningContinued" = "Soyez prudent avec des sources non officiels ! Ajoutez seulement des sources que vous jugez fiables.";
|
||||
"ConfirmAddSourceView.sourceURL" = "URL du source";
|
||||
"ConfirmAddSourceView.sourceInfo" = "Informations du source";
|
||||
"ConfirmAddSourceView.addSource" = "Ajouter une source";
|
||||
"AppPillButton.open" = "Ouvrir";
|
||||
|
||||
/* AppAction */
|
||||
"AppAction.install" = "Installer";
|
||||
"AppAction.open" = "Ouvrir";
|
||||
"AppAction.refresh" = "Rafraîchir";
|
||||
"AppAction.activate" = "Activer";
|
||||
"AppAction.deactivate" = "Désactiver";
|
||||
"AppAction.remove" = "Enlever";
|
||||
"AppAction.enableJIT" = "Activer JIT";
|
||||
"AppAction.backup" = "Sauvegarde";
|
||||
"MyAppsView.failedToRefresh" = "Rafraîchissement échoué";
|
||||
"MyAppsView.myApps" = "Mes Applications";
|
||||
"BrowseView.Categories.gamesAndEmulators" = "Jeux et\némulateurs";
|
||||
"MyAppsView.appIDsRemaining" = "Identifiants d'applications restantes";
|
||||
"MyAppsView.apps" = "apps"; /* Keep this lowercase */
|
||||
"MyAppsView.viewAppIDs" = "Voir les identifiants d'applications";
|
||||
|
||||
/* ConfirmAddSourceView */
|
||||
"ConfirmAddSourceView.apps" = "Applications";
|
||||
|
||||
/* AppPillButton */
|
||||
"AppPillButton.free" = "Gratuit";
|
||||
128
AltStore/Resources/hi.lproj/Localizable.strings
Normal file
128
AltStore/Resources/hi.lproj/Localizable.strings
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
AltStore
|
||||
|
||||
Created by Fabian Thies on 22.12.22.
|
||||
|
||||
Modified by mindfreakdev on 26.12.22
|
||||
|
||||
Copyright © 2022 SideStore. All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
/* NewsView */
|
||||
"NewsView.title" = "समाचार";
|
||||
"NewsView.Section.FromSources.title" = "अपने सूत्रों से";
|
||||
|
||||
|
||||
/* BrowseView */
|
||||
"BrowseView.title" = "ब्राउज़";
|
||||
"BrowseView.search" = "खोज";
|
||||
"BrowseView.Section.PromotedCategories.title" = "प्रचारित श्रेणियां";
|
||||
"BrowseView.Section.PromotedCategories.showAll" = "सब दिखाएं";
|
||||
"BrowseView.Section.AllApps.title" = "सभी एप्लीकेशन";
|
||||
"BrowseView.Actions.sources" = "सूत्रों का कहना है";
|
||||
"BrowseView.Categories.gamesAndEmulators" = "गेम्स और\nएम्युलेटर्स";
|
||||
|
||||
/* RootView */
|
||||
"RootView.news" = "समाचार";
|
||||
"RootView.browse" = "ब्राउज़";
|
||||
"RootView.myApps" = "मेरी एप्प्स";
|
||||
"RootView.settings" = "समायोजन";
|
||||
|
||||
/* SettingsView */
|
||||
"SettingsView.ConnectedAppleID.name" = "नाम";
|
||||
"SettingsView.ConnectedAppleID.eMail" = "ईमेल";
|
||||
"SettingsView.ConnectedAppleID.type" = "टाइप";
|
||||
"SettingsView.ConnectedAppleID.text" = "कनेक्टेड ऐप्पल आईडी";
|
||||
"SettingsView.ConnectedAppleID.signOut" = "प्रस्थान करें";
|
||||
"SettingsView.ConnectedAppleID.Footer.p1" = "आपके द्वारा SideStore के साथ इंस्टॉल किए गए ऐप्स पर हस्ताक्षर करने के लिए आपकी ऐप्पल आईडी आवश्यक है।";
|
||||
"SettingsView.ConnectedAppleID.Footer.p2" = "आपके क्रेडेंशियल्स केवल Apple के सर्वरों को भेजे जाते हैं और SideStore टीम द्वारा एक्सेस नहीं किए जा सकते हैं। एक बार सफलतापूर्वक लॉग इन करने के बाद, लॉगिन विवरण आपके डिवाइस पर सुरक्षित रूप से संग्रहीत हो जाते हैं।";
|
||||
|
||||
"SettingsView.connectAppleID" = "अपनी ऐप्पल आईडी कनेक्ट करें";
|
||||
"SettingsView.backgroundRefresh" = "बैकग्राउंड रिफ्रेश";
|
||||
"SettingsView.addToSiri" = "सिरी में जोड़ें...";
|
||||
"SettingsView.refreshingApps" = "ताज़ा करने वाले ऐप्स";
|
||||
"SettingsView.switchToUIKit" = "यूआईकिट पर स्विच करें";
|
||||
"SettingsView.resetImageCache" = "छवि कैश रीसेट करें";
|
||||
"SettingsView.debug" = "डिबग";
|
||||
"SettingsView.swiftUIRedesign" = "स्विफ्टयूआई रिडिजाइन";
|
||||
"SettingsView.credits" = "क्रेडिट";
|
||||
"SettingsView.title" = "समायोजन";
|
||||
"SettingsView.refreshingAppsFooter" = "वाई-फाई से कनेक्ट होने और वायरगार्ड सक्रिय होने पर बैकग्राउंड में ऐप्स को स्वचालित रूप से रीफ्रेश करने के लिए बैकग्राउंड रिफ्रेश सक्षम करें।";
|
||||
|
||||
/* ConnectAppleIDView */
|
||||
"ConnectAppleIDView.startWithSignIn" = "आरंभ करने के लिए अपने Apple ID से साइन इन करें।";
|
||||
"ConnectAppleIDView.signIn" = "साइन इन करें";
|
||||
"ConnectAppleIDView.appleID" = "ऐप्पल आईडी";
|
||||
"ConnectAppleIDView.password" = "कुंजिका";
|
||||
"ConnectAppleIDView.whyDoWeNeedThis" = "हमें इसकी ज़रूरत क्यों है?";
|
||||
"ConnectAppleIDView.footer" = "आपकी Apple ID का उपयोग ऐप्स को कॉन्फ़िगर करने के लिए किया जाता है ताकि उन्हें इस डिवाइस पर इंस्टॉल किया जा सके। आपके क्रेडेंशियल इस डिवाइस के कीचेन में सुरक्षित रूप से स्टोर किए जाएंगे और प्रमाणीकरण के लिए केवल Apple को भेजे जाएंगे।";
|
||||
"ConnectAppleIDView.connectYourAppleID" = "अपनी ऐप्पल आईडी कनेक्ट करें";
|
||||
"ConnectAppleIDView.cancel" = "रद्द करना";
|
||||
"ConnectAppleIDView.failedToSignIn" = "साइन इन करने में विफल";
|
||||
|
||||
/* SourcesView */
|
||||
"SourcesView.sourcesDescription" = "स्रोत नियंत्रित करते हैं कि कौन से ऐप्स SideStore के माध्यम से डाउनलोड करने के लिए उपलब्ध हैं।";
|
||||
"SourcesView.remove" = "हटाना";
|
||||
"SourcesView.trustedSources" = "विश्वसनीय स्रोत";
|
||||
"SourcesView.reviewedText" = "SideStore ने यह सुनिश्चित करने के लिए इन स्रोतों की समीक्षा की है कि वे हमारे सुरक्षा मानकों को पूरा करते हैं।";
|
||||
"SourcesView.sources" = "सूत्रों का कहना है";
|
||||
"SourcesView.done" = "पूर्ण";
|
||||
|
||||
/* AddSourceView */
|
||||
"AddSourceView.sourceURL" = "स्रोत यूआरएल";
|
||||
"AddSourceView.sourceWarning" = "कृपया यहां स्रोत url दर्ज करें। फिर, अगले चरण में स्रोत को सत्यापित करने और जोड़ने के लिए जारी रखें पर टैप करें।";
|
||||
"AddSourceView.sourceWarningContinued" = "अमान्य तृतीय पक्ष स्रोतों से सावधान रहें! केवल उन स्रोतों को जोड़ना सुनिश्चित करें जिन पर आप भरोसा करते हैं।";
|
||||
"AddSourceView.continue" = "जारी रखना";
|
||||
"AddSourceView.title" = "स्रोत जोड़ें";
|
||||
|
||||
/* ConfirmAddSourceView */
|
||||
"ConfirmAddSourceView.apps" = "ऐप्स";
|
||||
"ConfirmAddSourceView.newsItems" = "समाचार आइटम";
|
||||
"ConfirmAddSourceView.sourceContents" = "स्रोत सामग्री";
|
||||
"ConfirmAddSourceView.sourceIdentifier" = "स्रोत पहचानकर्ता";
|
||||
"ConfirmAddSourceView.sourceURL" = "स्रोत यूआरएल";
|
||||
"ConfirmAddSourceView.sourceInfo" = "स्रोत की जानकारी";
|
||||
"ConfirmAddSourceView.addSource" = "स्रोत जोड़ें";
|
||||
|
||||
/* AppPillButton */
|
||||
"AppPillButton.free" = "नि शुल्क";
|
||||
"AppPillButton.open" = "खुला";
|
||||
|
||||
/* AppAction */
|
||||
"AppAction.install" = "स्थापित करना";
|
||||
"AppAction.open" = "खुला";
|
||||
"AppAction.refresh" = "ताज़ा करना";
|
||||
"AppAction.activate" = "सक्रिय";
|
||||
"AppAction.deactivate" = "निष्क्रिय करें";
|
||||
"AppAction.remove" = "हटाना";
|
||||
"AppAction.enableJIT" = "सक्रिय JIT";
|
||||
"AppAction.backup" = "बैकअप";
|
||||
"AppAction.exportBackup" = "बैकअप निर्यात करें";
|
||||
"AppAction.restoreBackup" = "बैकअप बहाल";
|
||||
"AppAction.chooseCustomIcon" = "आइकन अनुकूलित करें";
|
||||
"AppAction.resetIcon" = "रीसेट आइकन";
|
||||
|
||||
/* AppDetailView*/
|
||||
"AppDetailView.more" = "अधिक...";
|
||||
"AppDetailView.whatsNew" = "नया क्या है";
|
||||
"AppDetailView.version" = "संस्करण";
|
||||
"AppDetailView.noVersionInformation" = "कोई संस्करण जानकारी नहीं";
|
||||
"AppDetailView.noPermissions" = "ऐप को किसी अनुमति की आवश्यकता नहीं है।";
|
||||
"AppDetailView.permissions" = "अनुमतियां";
|
||||
|
||||
/* AppPermissionGrid */
|
||||
"AppPermissionGrid.usageDescription" = "उपयोग विवरण";
|
||||
|
||||
/* MyAppsView */
|
||||
"MyAppsView.active" = "सक्रिय";
|
||||
"MyAppsView.sideloading" = "साइडलोडिंग प्रगति पर है...";
|
||||
"MyAppsView.refreshAll" = "सभी को रीफ्रेश करें";
|
||||
"MyAppsView.remainingAppID" = "शेष ऐप आईडी";
|
||||
"MyAppsView.appIDsRemaining" = "ऐप आईडी शेष";
|
||||
"MyAppsView.noUpdatesAvailable" = "कोई अपडेट उपलब्ध नहीं";
|
||||
"MyAppsView.failedToRefresh" = "रीफ़्रेश करने में विफल";
|
||||
"MyAppsView.apps" = "ऐप्स"; /* Keep this lowercase */
|
||||
"MyAppsView.viewAppIDs" = "ऐप आईडी देखें";
|
||||
"MyAppsView.myApps" = "मेरी एप्प्स";
|
||||
117
AltStore/Resources/ko.lproj/Localizable.strings
Normal file
117
AltStore/Resources/ko.lproj/Localizable.strings
Normal file
@@ -0,0 +1,117 @@
|
||||
|
||||
|
||||
"AppAction.enableJIT" = "JIT 사용";
|
||||
"AppAction.backup" = "백업";
|
||||
|
||||
|
||||
/* BrowseView */
|
||||
"BrowseView.title" = "탐럼하기";
|
||||
"BrowseView.search" = "검색";
|
||||
"BrowseView.Section.PromotedCategories.title" = "추천된 앱 종류";
|
||||
"BrowseView.Section.PromotedCategories.showAll" = "모두 보기";
|
||||
"BrowseView.Section.AllApps.title" = "모두 보기";
|
||||
"BrowseView.Actions.sources" = "소스";
|
||||
"BrowseView.Categories.gamesAndEmulators" = "게임과\n에뮬레이터";
|
||||
|
||||
/* RootView */
|
||||
"RootView.news" = "뉴즈";
|
||||
"RootView.browse" = "탐험하기";
|
||||
"RootView.myApps" = "설치된 앱";
|
||||
"RootView.settings" = "설정";
|
||||
"SettingsView.ConnectedAppleID.eMail" = "현재 연동된 Apple ID의 이매일";
|
||||
"SettingsView.ConnectedAppleID.type" = "종류";
|
||||
"SettingsView.ConnectedAppleID.text" = "현재 연동된 Apple ID";
|
||||
"SettingsView.ConnectedAppleID.signOut" = "Apple ID에서 로그아웃하기";
|
||||
"ConfirmAddSourceView.addSource" = "소스추가";
|
||||
|
||||
/* AppPillButton */
|
||||
"AppPillButton.free" = "무료";
|
||||
|
||||
/* AppAction */
|
||||
"AppAction.install" = "설치";
|
||||
"AppAction.open" = "열기";
|
||||
"AppPillButton.open" = "열기";
|
||||
"AppAction.refresh" = "재검색";
|
||||
"AppAction.activate" = "활성화";
|
||||
"AppAction.deactivate" = "비활성화";
|
||||
"AppAction.remove" = "지우기";
|
||||
"ConnectAppleIDView.appleID" = "Apple ID 이메일";
|
||||
"SettingsView.connectAppleID" = "Apple ID와 연동하기";
|
||||
"SettingsView.backgroundRefresh" = "백그라운드 사인";
|
||||
"SettingsView.refreshingApps" = "앱 다시 사인 하는중";
|
||||
"SettingsView.addToSiri" = "시리에 추가하기...";
|
||||
"SettingsView.switchToUIKit" = "UIkit을 사용하기";
|
||||
"SettingsView.resetImageCache" = "사진 다시 연동하기";
|
||||
"SettingsView.debug" = "디버그";
|
||||
"SettingsView.swiftUIRedesign" = "SwiftUI 리디다인";
|
||||
"SettingsView.credits" = "크래딧";
|
||||
"SettingsView.title" = "설정";
|
||||
"ConnectAppleIDView.signIn" = "로그인";
|
||||
"ConnectAppleIDView.password" = "비밀번호";
|
||||
"ConnectAppleIDView.whyDoWeNeedThis" = "정보수집을 왜하지?";
|
||||
"ConnectAppleIDView.connectYourAppleID" = "Apple ID 연동하기";
|
||||
"ConnectAppleIDView.cancel" = "취소";
|
||||
"ConnectAppleIDView.failedToSignIn" = "로그인 실패";
|
||||
"SourcesView.remove" = "삭제";
|
||||
|
||||
/* SourcesView */
|
||||
"SourcesView.sourcesDescription" = "소스는 SideStore에서 설치 가능한 앱을 설정합니다.";
|
||||
"SourcesView.trustedSources" = "검증소스";
|
||||
"SourcesView.reviewedText" = "이 소스는 SideStore에서 점검해 안전한 앱이 있습니다.";
|
||||
"SourcesView.sources" = "소스";
|
||||
"SourcesView.done" = "완료";
|
||||
|
||||
/* AddSourceView */
|
||||
"AddSourceView.sourceURL" = "소스 URL";
|
||||
"AddSourceView.sourceWarningContinued" = "미검증 소스를 함부로 추가하지 마세요.";
|
||||
"AddSourceView.sourceWarning" = "소스 URL을 입력하고, 작동 검증한 뒤 추가해주세요.";
|
||||
"AddSourceView.continue" = "추가";
|
||||
"AddSourceView.title" = "소스 추고";
|
||||
|
||||
/* ConfirmAddSourceView */
|
||||
"ConfirmAddSourceView.apps" = "앱";
|
||||
"ConfirmAddSourceView.newsItems" = "뉴스";
|
||||
"ConfirmAddSourceView.sourceContents" = "소스 내용물";
|
||||
"ConfirmAddSourceView.sourceIdentifier" = "소스 식별";
|
||||
"ConfirmAddSourceView.sourceURL" = "소스 URL";
|
||||
"ConfirmAddSourceView.sourceInfo" = "소스 정보";
|
||||
|
||||
/* NewsView */
|
||||
"NewsView.title" = "뉴스";
|
||||
"NewsView.Section.FromSources.title" = "소스에서";
|
||||
|
||||
/* SettingsView */
|
||||
"SettingsView.ConnectedAppleID.name" = "현재 연동된 Apple ID의 이름";
|
||||
"SettingsView.ConnectedAppleID.Footer.p1" = "이 앱을 사용하려면, Apple ID에 로그인을 해서 앱을 인증을 할 수 있습니다.";
|
||||
"AppAction.exportBackup" = "백업 내보내기";
|
||||
"ConnectAppleIDView.footer" = "당신의 Apple ID는 앱을 설정하여 기기에 설치가 가능하게 합니다. 당신의 개인정보는 디바이스의 Keychain에만 저장되며 애플외 전송되지 않습니다.";
|
||||
"SettingsView.ConnectedAppleID.Footer.p2" = "당신의 개인정보는 애플 외에는 전송되지 않으며, 로그인이 완료된 후에 보안이 강하게 저장됨니다.";
|
||||
"AppAction.restoreBackup" = "백업 받기";
|
||||
"AppAction.chooseCustomIcon" = "아이콘 바꾸기";
|
||||
"SettingsView.refreshingAppsFooter" = "백그라운드 앱 샤로고침 키면 와이파이와 Wireguard가켜진상태에 앱을 라프래쉬합니다.";
|
||||
"AppAction.resetIcon" = "유저 아이콘 삭재";
|
||||
|
||||
/* ConnectAppleIDView */
|
||||
"ConnectAppleIDView.startWithSignIn" = "Apple ID에 로그인을 하여 시작합시다.";
|
||||
"AppDetailView.whatsNew" = "무엇이 새로운가";
|
||||
"AppDetailView.version" = "버전";
|
||||
"AppDetailView.noVersionInformation" = "버전정보 없움";
|
||||
"AppDetailView.noPermissions" = "앱은 추가 기능이 필요 없다.";
|
||||
"AppDetailView.permissions" = "추가 기능";
|
||||
|
||||
/* AppPermissionGrid */
|
||||
"AppPermissionGrid.usageDescription" = "사용량";
|
||||
|
||||
/* MyAppsView */
|
||||
"MyAppsView.active" = "활성화";
|
||||
"MyAppsView.noUpdatesAvailable" = "업데이트 없움";
|
||||
"MyAppsView.failedToRefresh" = "업대이트 실패";
|
||||
|
||||
/* AppDetailView*/
|
||||
"AppDetailView.more" = "더 보기...";
|
||||
"MyAppsView.refreshAll" = "모두 다시 검색";
|
||||
"MyAppsView.appIDsRemaining" = "사용 가능한 App ID 개수";
|
||||
"MyAppsView.apps" = "apps"; /* Keep this lowercase */
|
||||
"MyAppsView.viewAppIDs" = "사용한 App ID보기";
|
||||
"MyAppsView.myApps" = "나의 앱";
|
||||
"MyAppsView.sideloading" = "설치 중...";
|
||||
128
AltStore/Resources/nl.lproj/Localizable.strings
Normal file
128
AltStore/Resources/nl.lproj/Localizable.strings
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
AltStore
|
||||
|
||||
Created by Fabian Thies on 22.12.22.
|
||||
|
||||
Modified by mindfreakdev on 25.12.22
|
||||
|
||||
Copyright © 2022 SideStore. All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
/* NewsView */
|
||||
"NewsView.title" = "Nieuws";
|
||||
"NewsView.Section.FromSources.title" = "Van je bronnen";
|
||||
|
||||
|
||||
/* BrowseView */
|
||||
"BrowseView.title" = "Bladeren";
|
||||
"BrowseView.search" = "Zoekopdracht";
|
||||
"BrowseView.Section.PromotedCategories.title" = "Gepromoveerde categorieën";
|
||||
"BrowseView.Section.PromotedCategories.showAll" = "Toon alles";
|
||||
"BrowseView.Section.AllApps.title" = "Alle Apps";
|
||||
"BrowseView.Actions.sources" = "Bronnen";
|
||||
"BrowseView.Categories.gamesAndEmulators" = "Spellen en\nEmulators";
|
||||
|
||||
/* RootView */
|
||||
"RootView.news" = "Nieuws";
|
||||
"RootView.browse" = "Bladeren";
|
||||
"RootView.myApps" = "Mijn apps";
|
||||
"RootView.settings" = "Instellingen";
|
||||
|
||||
/* SettingsView */
|
||||
"SettingsView.ConnectedAppleID.name" = "Naam";
|
||||
"SettingsView.ConnectedAppleID.eMail" = "E-mailen";
|
||||
"SettingsView.ConnectedAppleID.type" = "Type";
|
||||
"SettingsView.ConnectedAppleID.text" = "Verbonden Apple ID";
|
||||
"SettingsView.ConnectedAppleID.signOut" = "Afmelden";
|
||||
"SettingsView.ConnectedAppleID.Footer.p1" = "Uw Apple ID is vereist om de apps te ondertekenen die u met SideStore installeert.";
|
||||
"SettingsView.ConnectedAppleID.Footer.p2" = "Uw inloggegevens worden alleen naar de servers van Apple verzonden en zijn niet toegankelijk voor het SideStore-team. Nadat u succesvol bent ingelogd, worden de inloggegevens veilig op uw apparaat opgeslagen.";
|
||||
|
||||
"SettingsView.connectAppleID" = "Koppel uw Apple ID";
|
||||
"SettingsView.backgroundRefresh" = "Achtergrond Vernieuwen";
|
||||
"SettingsView.addToSiri" = "Toevoegen aan Siri...";
|
||||
"SettingsView.refreshingApps" = "Verfrissende Apps";
|
||||
"SettingsView.switchToUIKit" = "Schakel over naar UIKit";
|
||||
"SettingsView.resetImageCache" = "Stel De Afbeeldingscache Opnieuw In";
|
||||
"SettingsView.debug" = "Debuggen";
|
||||
"SettingsView.swiftUIRedesign" = "SwiftUI Herontwerp";
|
||||
"SettingsView.credits" = "Credits";
|
||||
"SettingsView.title" = "Instellingen";
|
||||
"SettingsView.refreshingAppsFooter" = "Schakel Achtergrondvernieuwing in om apps op de achtergrond automatisch te vernieuwen wanneer ze verbonden zijn met wifi en Wireguard actief is.";
|
||||
|
||||
/* ConnectAppleIDView */
|
||||
"ConnectAppleIDView.startWithSignIn" = "Aanmelden met uw Apple ID om aan de slag te gaan.";
|
||||
"ConnectAppleIDView.signIn" = "Aanmelden";
|
||||
"ConnectAppleIDView.appleID" = "Apple ID";
|
||||
"ConnectAppleIDView.password" = "Wachtwoord";
|
||||
"ConnectAppleIDView.whyDoWeNeedThis" = "Waarom hebben we dit nodig?";
|
||||
"ConnectAppleIDView.footer" = "Uw Apple ID wordt gebruikt om apps te configureren zodat ze op dit apparaat kunnen worden geïnstalleerd. Uw inloggegevens worden veilig opgeslagen in de sleutelhanger van dit apparaat en alleen naar Apple verzonden voor authenticatie.";
|
||||
"ConnectAppleIDView.connectYourAppleID" = "Verbind uw Apple ID";
|
||||
"ConnectAppleIDView.cancel" = "Annuleren";
|
||||
"ConnectAppleIDView.failedToSignIn" = "Aanmelden mislukt";
|
||||
|
||||
/* SourcesView */
|
||||
"SourcesView.sourcesDescription" = "Bronnen bepalen welke apps beschikbaar zijn om te downloaden via SideStore.";
|
||||
"SourcesView.remove" = "Verwijderen";
|
||||
"SourcesView.trustedSources" = "Vertrouwde Bronnen";
|
||||
"SourcesView.reviewedText" = "SideStore heeft deze bronnen beoordeeld om er zeker van te zijn dat ze voldoen aan onze veiligheidsnormen.";
|
||||
"SourcesView.sources" = "Bronnen";
|
||||
"SourcesView.done" = "Gedaan";
|
||||
|
||||
/* AddSourceView */
|
||||
"AddSourceView.sourceURL" = "Bron URL";
|
||||
"AddSourceView.sourceWarning" = "Voer hier de bron-URL in. Tik vervolgens op doorgaan om te valideren en voeg de bron toe in de volgende stap.";
|
||||
"AddSourceView.sourceWarningContinued" = "Wees voorzichtig met niet-gevalideerde bronnen van derden! Zorg ervoor dat u alleen bronnen toevoegt die u vertrouwt.";
|
||||
"AddSourceView.continue" = "Doorgaan";
|
||||
"AddSourceView.title" = "Bron Toevoegen";
|
||||
|
||||
/* ConfirmAddSourceView */
|
||||
"ConfirmAddSourceView.apps" = "Apps";
|
||||
"ConfirmAddSourceView.newsItems" = "Nieuwsberichten";
|
||||
"ConfirmAddSourceView.sourceContents" = "Bron Inhoud";
|
||||
"ConfirmAddSourceView.sourceIdentifier" = "Bron Identificatie";
|
||||
"ConfirmAddSourceView.sourceURL" = "Bron URL";
|
||||
"ConfirmAddSourceView.sourceInfo" = "Bron Informatie";
|
||||
"ConfirmAddSourceView.addSource" = "Bron Toevoegen";
|
||||
|
||||
/* AppPillButton */
|
||||
"AppPillButton.free" = "Vrij";
|
||||
"AppPillButton.open" = "Open";
|
||||
|
||||
/* AppAction */
|
||||
"AppAction.install" = "Installeren";
|
||||
"AppAction.open" = "Open";
|
||||
"AppAction.refresh" = "Vernieuwen";
|
||||
"AppAction.activate" = "Activeren";
|
||||
"AppAction.deactivate" = "Deactiveren";
|
||||
"AppAction.remove" = "Verwijderen";
|
||||
"AppAction.enableJIT" = "Activeer JIT";
|
||||
"AppAction.backup" = "Backup";
|
||||
"AppAction.exportBackup" = "Backup exporteren";
|
||||
"AppAction.restoreBackup" = "Backup terugzetten";
|
||||
"AppAction.chooseCustomIcon" = "Pictogram aanpassen";
|
||||
"AppAction.resetIcon" = "Pictogram resetten";
|
||||
|
||||
/* AppDetailView*/
|
||||
"AppDetailView.more" = "Meer...";
|
||||
"AppDetailView.whatsNew" = "Wat Is Er Nieuw";
|
||||
"AppDetailView.version" = "Versie";
|
||||
"AppDetailView.noVersionInformation" = "Geen versie informatie";
|
||||
"AppDetailView.noPermissions" = "De app vereist geen machtigingen.";
|
||||
"AppDetailView.permissions" = "Rechten";
|
||||
|
||||
/* AppPermissionGrid */
|
||||
"AppPermissionGrid.usageDescription" = "Gebruik Beschrijving";
|
||||
|
||||
/* MyAppsView */
|
||||
"MyAppsView.active" = "Actief";
|
||||
"MyAppsView.sideloading" = "Bezig met sideloaden...";
|
||||
"MyAppsView.refreshAll" = "Ververs Alles";
|
||||
"MyAppsView.remainingAppID" = "Resterende App IDs";
|
||||
"MyAppsView.appIDsRemaining" = "App IDs Resterende";
|
||||
"MyAppsView.noUpdatesAvailable" = "Geen Updates Beschikbaar";
|
||||
"MyAppsView.failedToRefresh" = "Kan niet vernieuwen";
|
||||
"MyAppsView.apps" = "apps"; /* Keep this lowercase */
|
||||
"MyAppsView.viewAppIDs" = "App IDs Bekijken";
|
||||
"MyAppsView.myApps" = "Mijn Apps";
|
||||
128
AltStore/Resources/uk.lproj/Localizable.strings
Normal file
128
AltStore/Resources/uk.lproj/Localizable.strings
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
Localizable.strings
|
||||
AltStore
|
||||
|
||||
Created by Fabian Thies on 22.12.22.
|
||||
|
||||
Modified by mindfreakdev on 25.12.22
|
||||
|
||||
Copyright © 2022 SideStore. All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
/* NewsView */
|
||||
"NewsView.title" = "Новини";
|
||||
"NewsView.Section.FromSources.title" = "З ваших джерел";
|
||||
|
||||
|
||||
/* BrowseView */
|
||||
"BrowseView.title" = "переглядати";
|
||||
"BrowseView.search" = "Пошук";
|
||||
"BrowseView.Section.PromotedCategories.title" = "Розширені категорії";
|
||||
"BrowseView.Section.PromotedCategories.showAll" = "Показати все";
|
||||
"BrowseView.Section.AllApps.title" = "Усі додатки";
|
||||
"BrowseView.Actions.sources" = "Джерела";
|
||||
"BrowseView.Categories.gamesAndEmulators" = "Ігри та\nемулятори";
|
||||
|
||||
/* RootView */
|
||||
"RootView.news" = "Новини";
|
||||
"RootView.browse" = "переглядати";
|
||||
"RootView.myApps" = "Мої програми";
|
||||
"RootView.settings" = "Налаштування";
|
||||
|
||||
/* SettingsView */
|
||||
"SettingsView.ConnectedAppleID.name" = "Ім'я";
|
||||
"SettingsView.ConnectedAppleID.eMail" = "Електронна пошта";
|
||||
"SettingsView.ConnectedAppleID.type" = "Тип";
|
||||
"SettingsView.ConnectedAppleID.text" = "Підключений Apple ID";
|
||||
"SettingsView.ConnectedAppleID.signOut" = "Вийти з аккаунта";
|
||||
"SettingsView.ConnectedAppleID.Footer.p1" = "Ваш Apple ID потрібен для підпису програм, які ви встановлюєте за допомогою SideStore.";
|
||||
"SettingsView.ConnectedAppleID.Footer.p2" = "Ваші облікові дані надсилаються лише на сервери Apple і недоступні для команди SideStore. Після успішного входу дані для входу надійно зберігаються на вашому пристрої.";
|
||||
|
||||
"SettingsView.connectAppleID" = "Підключіть свій Apple ID";
|
||||
"SettingsView.backgroundRefresh" = "Фонове оновлення";
|
||||
"SettingsView.addToSiri" = "Додати до Siri...";
|
||||
"SettingsView.refreshingApps" = "Оновлення програм";
|
||||
"SettingsView.switchToUIKit" = "Перейдіть на UIKit";
|
||||
"SettingsView.resetImageCache" = "Скинути кеш зображень";
|
||||
"SettingsView.debug" = "Відлагоджувати";
|
||||
"SettingsView.swiftUIRedesign" = "Редизайн SwiftUI";
|
||||
"SettingsView.credits" = "Кредити";
|
||||
"SettingsView.title" = "Налаштування";
|
||||
"SettingsView.refreshingAppsFooter" = "Увімкніть Background Refresh, щоб автоматично оновлювати програми у фоновому режимі при підключенні до Wi-Fi і Wireguard.";
|
||||
|
||||
/* ConnectAppleIDView */
|
||||
"ConnectAppleIDView.startWithSignIn" = "Щоб почати, увійдіть за допомогою свого Apple ID.";
|
||||
"ConnectAppleIDView.signIn" = "Увійти";
|
||||
"ConnectAppleIDView.appleID" = "Apple ID";
|
||||
"ConnectAppleIDView.password" = "Пароль";
|
||||
"ConnectAppleIDView.whyDoWeNeedThis" = "Навіщо нам це?";
|
||||
"ConnectAppleIDView.footer" = "Ваш Apple ID використовується для налаштування додатків, щоб їх можна було встановити на цьому пристрої. Ваші облікові дані будуть безпечно зберігатися в Keychain цього пристрою та надсилатися лише в Apple для автентифікації.";
|
||||
"ConnectAppleIDView.connectYourAppleID" = "Підключіть свій Apple ID";
|
||||
"ConnectAppleIDView.cancel" = "Скасувати";
|
||||
"ConnectAppleIDView.failedToSignIn" = "Не вдалося ввійти";
|
||||
|
||||
/* SourcesView */
|
||||
"SourcesView.sourcesDescription" = "Джерела контролюють, які програми доступні для завантаження через SideStore.";
|
||||
"SourcesView.remove" = "видалити";
|
||||
"SourcesView.trustedSources" = "Надійні джерела";
|
||||
"SourcesView.reviewedText" = "SideStore перевірив ці джерела, щоб переконатися, що вони відповідають нашим стандартам безпеки.";
|
||||
"SourcesView.sources" = "Джерела";
|
||||
"SourcesView.done" = "Готово";
|
||||
|
||||
/* AddSourceView */
|
||||
"AddSourceView.sourceURL" = "Вихідна URL-адреса";
|
||||
"AddSourceView.sourceWarning" = "Введіть URL-адресу джерела тут. Потім натисніть «Продовжити», щоб перевірити та додати джерело на наступному кроці.";
|
||||
"AddSourceView.sourceWarningContinued" = "Будьте обережні з неперевіреними сторонніми джерелами! Додавайте лише ті джерела, яким довіряєте.";
|
||||
"AddSourceView.continue" = "Продовжити";
|
||||
"AddSourceView.title" = "Додати джерело";
|
||||
|
||||
/* ConfirmAddSourceView */
|
||||
"ConfirmAddSourceView.apps" = "програми";
|
||||
"ConfirmAddSourceView.newsItems" = "Новини";
|
||||
"ConfirmAddSourceView.sourceContents" = "Зміст джерела";
|
||||
"ConfirmAddSourceView.sourceIdentifier" = "Ідентифікатор джерела";
|
||||
"ConfirmAddSourceView.sourceURL" = "Вихідна URL-адреса";
|
||||
"ConfirmAddSourceView.sourceInfo" = "Вихідна інформація";
|
||||
"ConfirmAddSourceView.addSource" = "Додати джерело";
|
||||
|
||||
/* AppPillButton */
|
||||
"AppPillButton.free" = "безкоштовно";
|
||||
"AppPillButton.open" = "ВІДЧИНЕНО";
|
||||
|
||||
/* AppAction */
|
||||
"AppAction.install" = "встановити";
|
||||
"AppAction.open" = "ВІДЧИНЕНО";
|
||||
"AppAction.refresh" = "Оновити";
|
||||
"AppAction.activate" = "активувати";
|
||||
"AppAction.deactivate" = "Дезактивувати";
|
||||
"AppAction.remove" = "видалити";
|
||||
"AppAction.enableJIT" = "Активуйте JIT";
|
||||
"AppAction.backup" = "Резервне копіювання";
|
||||
"AppAction.exportBackup" = "Експорт резервної копії";
|
||||
"AppAction.restoreBackup" = "Відновлення резервної копії";
|
||||
"AppAction.chooseCustomIcon" = "Налаштувати значок";
|
||||
"AppAction.resetIcon" = "Значок скидання";
|
||||
|
||||
/* AppDetailView*/
|
||||
"AppDetailView.more" = "більше...";
|
||||
"AppDetailView.whatsNew" = "Що нового";
|
||||
"AppDetailView.version" = "Версія";
|
||||
"AppDetailView.noVersionInformation" = "Немає інформації про версію";
|
||||
"AppDetailView.noPermissions" = "Програма не потребує дозволів.";
|
||||
"AppDetailView.permissions" = "Дозволи";
|
||||
|
||||
/* AppPermissionGrid */
|
||||
"AppPermissionGrid.usageDescription" = "Опис використання";
|
||||
|
||||
/* MyAppsView */
|
||||
"MyAppsView.active" = "Активний";
|
||||
"MyAppsView.sideloading" = "Виконується стороннє завантаження...";
|
||||
"MyAppsView.refreshAll" = "Оновити все";
|
||||
"MyAppsView.remainingAppID" = "Решта ідентифікаторів програм";
|
||||
"MyAppsView.appIDsRemaining" = "Залишилося ідентифікаторів програм";
|
||||
"MyAppsView.noUpdatesAvailable" = "Немає оновлень";
|
||||
"MyAppsView.failedToRefresh" = "Не вдалося оновити";
|
||||
"MyAppsView.apps" = "програми"; /* Keep this lowercase */
|
||||
"MyAppsView.viewAppIDs" = "Перегляньте ідентифікатори програм";
|
||||
"MyAppsView.myApps" = "Мої програми";
|
||||
@@ -20,20 +20,24 @@
|
||||
<key>Titles</key>
|
||||
<array>
|
||||
<string>SideStore</string>
|
||||
<string>SideStore (.zip)</string>
|
||||
<string>SideStore (.xyz)</string>
|
||||
<string>Macley (US)</string>
|
||||
<string>Macley (DE)</string>
|
||||
<string>DrPudding</string>
|
||||
<string>Sideloadly</string>
|
||||
<string>Nick</string>
|
||||
<string>Jawshoeadan</string>
|
||||
<string>WesleyBryie</string>
|
||||
<string>crystall1nedev</string>
|
||||
</array>
|
||||
<key>Values</key>
|
||||
<array>
|
||||
<string>https://ani.sidestore.io</string>
|
||||
<string>https://ani.sidestore.zip</string>
|
||||
<string>https://ani.846969.xyz</string>
|
||||
<string>http://5.249.163.88:6969/</string>
|
||||
<string>http://us1.sternserv.tech</string>
|
||||
<string>http://de1.sternserv.tech</string>
|
||||
<string>https://sign.rheaa.xyz</string>
|
||||
<string>https://sideloadly.io/anisette/irGb3Quww8zrhgqnzmrx</string>
|
||||
<string>http://45.33.29.114</string>
|
||||
<string>https://anisette.jawshoeadan.me</string>
|
||||
<string>https://ani.wesbryie.com</string>
|
||||
<string>https://anisette.crystall1ne.software/</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<collectionReusableView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="AboutHeader" id="xq2-Pl-zaG" customClass="AboutPatreonHeaderView" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="390" height="682"/>
|
||||
<collectionReusableView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="AboutHeader" id="xq2-Pl-zaG" customClass="AboutPatreonHeaderView" customModule="AltStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="445"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" spacing="25" translatesAutoresizingMaskIntoConstraints="NO" id="XiA-Jf-XMp">
|
||||
<rect key="frame" x="16" y="2" width="358" height="630"/>
|
||||
<rect key="frame" x="16" y="2" width="343" height="393"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="5Ol-zN-wYv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="358" height="426"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="317"/>
|
||||
<subviews>
|
||||
<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="358" height="55"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="55"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="SideStore" translatesAutoresizingMaskIntoConstraints="NO" id="pn6-Ic-MJm">
|
||||
<rect key="frame" x="0.0" y="0.0" width="55" height="55"/>
|
||||
@@ -31,7 +31,7 @@
|
||||
</constraints>
|
||||
</imageView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="si2-MA-3RH">
|
||||
<rect key="frame" x="65" y="0.0" width="293" height="55"/>
|
||||
<rect key="frame" x="65" y="0.0" width="278" height="55"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="hkS-oz-wiT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="83" height="55"/>
|
||||
@@ -51,7 +51,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="TFB-qo-cbh">
|
||||
<rect key="frame" x="210" y="0.0" width="83" height="55"/>
|
||||
<rect key="frame" x="195" y="0.0" width="83" 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="83" height="50"/>
|
||||
@@ -75,13 +75,11 @@
|
||||
</constraints>
|
||||
</stackView>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FeG-e5-LJl">
|
||||
<rect key="frame" x="0.0" y="65" width="358" height="361"/>
|
||||
<rect key="frame" x="0.0" y="65" width="343" height="252"/>
|
||||
<color key="backgroundColor" white="1" alpha="0.13" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<string key="text">Thank you for using SideStore!
|
||||
<string key="text">Hello, thank you for using SideStore!
|
||||
|
||||
Subscribing to the patreon supports us and makes sure we can continue developing SideStore for you.
|
||||
|
||||
Following us on social media allows us to give quick updates and spread the word about sideloading!
|
||||
If you would subscribe to the patreon that would support us and make sure we can continue developing SideStore for you.
|
||||
|
||||
-SideTeam</string>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -91,10 +89,10 @@ Following us on social media allows us to give quick updates and spread the word
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="13" translatesAutoresizingMaskIntoConstraints="NO" id="QS9-vO-bj8">
|
||||
<rect key="frame" x="0.0" y="451" width="358" height="179"/>
|
||||
<rect key="frame" x="0.0" y="342" width="343" height="51"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yEi-L6-kQ8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="358" height="51"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="51"/>
|
||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="51" id="l4o-vb-cMy"/>
|
||||
@@ -104,28 +102,6 @@ Following us on social media allows us to give quick updates and spread the word
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hov-Ce-LaM" userLabel="Twitter Button">
|
||||
<rect key="frame" x="0.0" y="64" width="358" height="51"/>
|
||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="51" id="m0M-GX-KKG"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="19"/>
|
||||
<state key="normal" title="Follow us on Twitter!">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="VdY-7Q-amF" userLabel="Twitter Button">
|
||||
<rect key="frame" x="0.0" y="128" width="358" height="51"/>
|
||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="51" id="kDo-b8-6tZ"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="19"/>
|
||||
<state key="normal" title="Follow us on Instagram!">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
@@ -138,21 +114,19 @@ Following us on social media allows us to give quick updates and spread the word
|
||||
<constraint firstItem="XiA-Jf-XMp" firstAttribute="top" secondItem="xq2-Pl-zaG" secondAttribute="top" constant="2" id="j8p-JX-Dcz"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="instagramButton" destination="VdY-7Q-amF" id="5kj-9x-k4F"/>
|
||||
<outlet property="rileyImageView" destination="pn6-Ic-MJm" id="60i-Q0-ojz"/>
|
||||
<outlet property="rileyLabel" destination="DTd-Yu-HXr" id="O0y-JB-gWp"/>
|
||||
<outlet property="shaneLabel" destination="Zpb-k3-y7l" id="aQN-6B-s5T"/>
|
||||
<outlet property="supportButton" destination="yEi-L6-kQ8" id="Dzo-vd-SnD"/>
|
||||
<outlet property="textView" destination="FeG-e5-LJl" id="K0M-lF-I6u"/>
|
||||
<outlet property="twitterButton" destination="hov-Ce-LaM" id="gib-Lt-qtY"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="147.82608695652175" y="58.258928571428569"/>
|
||||
<point key="canvasLocation" x="138" y="138"/>
|
||||
</collectionReusableView>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="SideStore" width="1024" height="1024"/>
|
||||
<image name="SideStore" width="180" height="180"/>
|
||||
<namedColor name="SettingsHighlighted">
|
||||
<color red="0.38823529411764707" green="0.011764705882352941" blue="0.58823529411764708" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.23529411764705882" green="0.0" blue="0.40392156862745099" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -56,8 +56,6 @@ final class PatronsFooterView: UICollectionReusableView
|
||||
final class AboutPatreonHeaderView: UICollectionReusableView
|
||||
{
|
||||
@IBOutlet var supportButton: UIButton!
|
||||
@IBOutlet var twitterButton: UIButton!
|
||||
@IBOutlet var instagramButton: UIButton!
|
||||
@IBOutlet var accountButton: UIButton!
|
||||
@IBOutlet var textView: UITextView!
|
||||
|
||||
@@ -81,12 +79,12 @@ final class AboutPatreonHeaderView: UICollectionReusableView
|
||||
imageView.layer.cornerRadius = imageView.bounds.midY
|
||||
}
|
||||
|
||||
for button in [self.supportButton, self.accountButton, self.twitterButton, self.instagramButton].compactMap({ $0 })
|
||||
for button in [self.supportButton, self.accountButton].compactMap({ $0 })
|
||||
{
|
||||
button.clipsToBounds = true
|
||||
button.layer.cornerRadius = 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func layoutMarginsDidChange()
|
||||
{
|
||||
|
||||
@@ -111,9 +111,7 @@ private extension PatreonViewController
|
||||
headerView.layoutMargins = self.view.layoutMargins
|
||||
|
||||
headerView.supportButton.addTarget(self, action: #selector(PatreonViewController.openPatreonURL(_:)), for: .primaryActionTriggered)
|
||||
headerView.twitterButton.addTarget(self, action: #selector(PatreonViewController.openTwitterURL(_:)), for: .primaryActionTriggered)
|
||||
headerView.instagramButton.addTarget(self, action: #selector(PatreonViewController.openInstagramURL(_:)), for: .primaryActionTriggered)
|
||||
|
||||
|
||||
let defaultSupportButtonTitle = NSLocalizedString("Become a patron", comment: "")
|
||||
let isPatronSupportButtonTitle = NSLocalizedString("View Patreon", comment: "")
|
||||
|
||||
@@ -182,24 +180,6 @@ private extension PatreonViewController
|
||||
self.present(safariViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc func openTwitterURL(_ sender: UIButton)
|
||||
{
|
||||
let twitterURL = URL(string: "https://twitter.com/SideStore_io")!
|
||||
|
||||
let safariViewController = SFSafariViewController(url: twitterURL)
|
||||
safariViewController.preferredControlTintColor = self.view.tintColor
|
||||
self.present(safariViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc func openInstagramURL(_ sender: UIButton)
|
||||
{
|
||||
let twitterURL = URL(string: "https://instagram.com/sidestore.io")!
|
||||
|
||||
let safariViewController = SFSafariViewController(url: twitterURL)
|
||||
safariViewController.preferredControlTintColor = self.view.tintColor
|
||||
self.present(safariViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@IBAction func authenticate(_ sender: UIBarButtonItem)
|
||||
{
|
||||
PatreonAPI.shared.authenticate { (result) in
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
||||
<rect key="frame" x="0.0" y="1245" width="375" height="25"/>
|
||||
<rect key="frame" x="0.0" y="1194" width="375" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -167,8 +167,8 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Support the team" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Il-5a-5Zp">
|
||||
<rect key="frame" x="30" y="15.5" width="142.5" height="20.5"/>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Join the beta" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Il-5a-5Zp">
|
||||
<rect key="frame" x="30" y="15.5" width="106" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -236,50 +236,14 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="GYp-O0-pse" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="444" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="GYp-O0-pse" id="vDG-ZV-xRS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Disable Idle Timeout" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PCh-Up-aJJ">
|
||||
<rect key="frame" x="30" y="15.5" width="166" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iQA-wm-5ag">
|
||||
<rect key="frame" x="296" y="10" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleNoIdleTimeoutEnabled:" destination="aMk-Xp-UL8" eventType="valueChanged" id="WSl-Jc-g5J"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="iQA-wm-5ag" secondAttribute="trailing" id="MJ1-HF-Dln"/>
|
||||
<constraint firstItem="PCh-Up-aJJ" firstAttribute="leading" secondItem="vDG-ZV-xRS" secondAttribute="leadingMargin" id="Ocu-jn-RAQ"/>
|
||||
<constraint firstItem="iQA-wm-5ag" firstAttribute="centerY" secondItem="vDG-ZV-xRS" secondAttribute="centerY" id="c6W-bN-VAb"/>
|
||||
<constraint firstItem="PCh-Up-aJJ" firstAttribute="centerY" secondItem="vDG-ZV-xRS" secondAttribute="centerY" id="mL6-LB-cjn"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="2"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="amC-sE-8O0" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="495" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="444" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="amC-sE-8O0" id="GEO-2e-E4k">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Allow Siri To Refresh Apps…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6K-fI-CVr">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Add to Siri…" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6K-fI-CVr">
|
||||
<rect key="frame" x="30" y="15.5" width="100.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -305,7 +269,7 @@
|
||||
<tableViewSection headerTitle="" id="eHy-qI-w5w">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="30h-59-88f" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="586" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="535" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="30h-59-88f" id="7qD-DW-Jls">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -345,7 +309,7 @@
|
||||
<tableViewSection headerTitle="" id="J90-vn-u2O">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="i4T-2q-jF3" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="677" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="626" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="i4T-2q-jF3" id="VTQ-H4-aCM">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -389,7 +353,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="oHX-oR-nwJ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="728" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="677" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="oHX-oR-nwJ" id="hN4-i5-igu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
@@ -433,28 +397,28 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="0MT-ht-Sit" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="779" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="728" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0MT-ht-Sit" id="OZp-WM-5H7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Asset Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fGU-Fp-XgM">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Asset Designer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fGU-Fp-XgM">
|
||||
<rect key="frame" x="30" y="15.5" width="115.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" ambiguous="YES" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="R8B-DW-7mY">
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="R8B-DW-7mY">
|
||||
<rect key="frame" x="206" y="15.5" width="139" height="20.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Chris (LitRitt)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hId-3P-41T">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Chris (LitRitt)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hId-3P-41T">
|
||||
<rect key="frame" x="0.0" y="0.0" width="107" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="baq-cE-fMY">
|
||||
<rect key="frame" x="121" y="0.0" width="18" height="20.5"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -477,19 +441,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="830" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="779" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Licenses" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D6b-cd-pVK">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Licenses" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="D6b-cd-pVK">
|
||||
<rect key="frame" x="30" y="15.5" width="67.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="s79-GQ-khr">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="s79-GQ-khr">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -517,19 +481,19 @@
|
||||
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="921" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="870" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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="30" y="15.5" width="125.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -550,19 +514,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="972" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="921" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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.5" width="187.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -586,19 +550,19 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="rE2-P4-OaE" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1023" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="972" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rE2-P4-OaE" id="qIT-rz-ZUb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="View Error Log" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PWC-OG-5jx">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="View Error Log" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PWC-OG-5jx">
|
||||
<rect key="frame" x="30" y="15.5" width="119" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="VfB-c5-5wG">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="VfB-c5-5wG">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -618,56 +582,23 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<segue destination="g8a-Rf-zWa" kind="show" id="vFC-Id-Ww6"/>
|
||||
<segue destination="g8a-Rf-zWa" kind="show" identifier="showErrorLog" id="SSW-vL-86I"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="eZ3-BT-q4D" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1023" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="eZ3-BT-q4D" id="17m-VV-hzf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Clear Cache" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IbH-V1-ce3">
|
||||
<rect key="frame" x="30" y="15.5" width="98.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="FZe-BJ-fOm">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="FZe-BJ-fOm" firstAttribute="centerY" secondItem="17m-VV-hzf" secondAttribute="centerY" id="bGv-Np-5aO"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="FZe-BJ-fOm" secondAttribute="trailing" id="ccb-JP-Eqi"/>
|
||||
<constraint firstItem="IbH-V1-ce3" firstAttribute="centerY" secondItem="17m-VV-hzf" secondAttribute="centerY" id="iQJ-gN-sRF"/>
|
||||
<constraint firstItem="IbH-V1-ce3" firstAttribute="leading" secondItem="17m-VV-hzf" secondAttribute="leadingMargin" id="m1g-Y6-aT5"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="2"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1023" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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.5" width="140" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -688,19 +619,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1125" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Reset adi.pb" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eds-Dj-36y">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reset adi.pb" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eds-Dj-36y">
|
||||
<rect key="frame" x="30" y="15.5" width="102" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="0dh-yd-7i9">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="0dh-yd-7i9">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -721,19 +652,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1176" width="375" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1125" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fj2-EJ-Z98" id="BcT-Fs-KNg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Advanced Settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OcM-OM-uDE">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Advanced Settings" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OcM-OM-uDE">
|
||||
<rect key="frame" x="30" y="15.5" width="154" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Pcu-Sy-yfZ">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="Pcu-Sy-yfZ">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
@@ -758,6 +689,7 @@
|
||||
</sections>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="aMk-Xp-UL8" id="c6c-fR-8C4"/>
|
||||
<outlet property="delegate" destination="aMk-Xp-UL8" id="moP-1B-lRq"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<navigationItem key="navigationItem" title="Settings" id="Ddg-UQ-KJ8"/>
|
||||
@@ -766,7 +698,6 @@
|
||||
<outlet property="accountNameLabel" destination="CnN-M1-AYK" id="Ldc-Py-Bix"/>
|
||||
<outlet property="accountTypeLabel" destination="434-MW-Den" id="mNB-QE-4Jg"/>
|
||||
<outlet property="backgroundRefreshSwitch" destination="DPu-zD-Als" id="eiG-Hv-Vko"/>
|
||||
<outlet property="noIdleTimeoutSwitch" destination="iQA-wm-5ag" id="jHC-js-q0Y"/>
|
||||
<outlet property="versionLabel" destination="bUR-rp-Nw2" id="85I-5R-hqz"/>
|
||||
</connections>
|
||||
</tableViewController>
|
||||
@@ -986,7 +917,7 @@ Settings by i cons from the Noun Project</string>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1697" y="313"/>
|
||||
</scene>
|
||||
<!--Support us-->
|
||||
<!--Patreon-->
|
||||
<scene sceneID="Lnh-9P-HnL">
|
||||
<objects>
|
||||
<collectionViewController id="dp8-8j-vt9" customClass="PatreonViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||
@@ -1033,7 +964,7 @@ Settings by i cons from the Noun Project</string>
|
||||
<outlet property="delegate" destination="dp8-8j-vt9" id="790-Kr-6l7"/>
|
||||
</connections>
|
||||
</collectionView>
|
||||
<navigationItem key="navigationItem" title="Support us" largeTitleDisplayMode="always" id="uUV-1f-xEq"/>
|
||||
<navigationItem key="navigationItem" title="Patreon" largeTitleDisplayMode="always" id="uUV-1f-xEq"/>
|
||||
</collectionViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="qq3-Hj-S9f" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
|
||||
@@ -30,14 +30,13 @@ extension SettingsViewController
|
||||
fileprivate enum AppRefreshRow: Int, CaseIterable
|
||||
{
|
||||
case backgroundRefresh
|
||||
case noIdleTimeout
|
||||
|
||||
@available(iOS 14, *)
|
||||
case addToSiri
|
||||
|
||||
static var allCases: [AppRefreshRow] {
|
||||
guard #available(iOS 14, *) else { return [.backgroundRefresh, .noIdleTimeout] }
|
||||
return [.backgroundRefresh, .noIdleTimeout, .addToSiri]
|
||||
guard #available(iOS 14, *) else { return [.backgroundRefresh] }
|
||||
return [.backgroundRefresh, .addToSiri]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,11 +53,9 @@ extension SettingsViewController
|
||||
case sendFeedback
|
||||
case refreshAttempts
|
||||
case errorLog
|
||||
case clearCache
|
||||
case resetPairingFile
|
||||
case resetAdiPb
|
||||
case advancedSettings
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +73,6 @@ final class SettingsViewController: UITableViewController
|
||||
@IBOutlet private var accountTypeLabel: UILabel!
|
||||
|
||||
@IBOutlet private var backgroundRefreshSwitch: UISwitch!
|
||||
@IBOutlet private var noIdleTimeoutSwitch: UISwitch!
|
||||
|
||||
@IBOutlet private var versionLabel: UILabel!
|
||||
|
||||
@@ -106,33 +102,16 @@ final class SettingsViewController: UITableViewController
|
||||
debugModeGestureRecognizer.numberOfTouchesRequired = 3
|
||||
self.tableView.addGestureRecognizer(debugModeGestureRecognizer)
|
||||
|
||||
print(Bundle.main.infoDictionary)
|
||||
var versionString: String = ""
|
||||
if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
|
||||
{
|
||||
versionString += "SideStore \(version)"
|
||||
if let xcode = Bundle.main.object(forInfoDictionaryKey: "DTXcode") as? String {
|
||||
print(xcode)
|
||||
versionString += " - Xcode \(xcode) - "
|
||||
if let build = Bundle.main.object(forInfoDictionaryKey: "DTXcodeBuild") as? String {
|
||||
print(build)
|
||||
versionString += "\(build)"
|
||||
}
|
||||
}
|
||||
if let pairing = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String {
|
||||
let pair_test = pairing == "<insert pairing file here>"
|
||||
if !pair_test {
|
||||
versionString += " - \(!pair_test)"
|
||||
}
|
||||
}
|
||||
self.versionLabel.text = NSLocalizedString(String(format: "SideStore %@", version), comment: "SideStore Version")
|
||||
}
|
||||
else
|
||||
{
|
||||
versionString += "SideStore\t"
|
||||
self.versionLabel.text = NSLocalizedString("SideStore", comment: "")
|
||||
}
|
||||
self.versionLabel.text = NSLocalizedString(versionString, comment: "SideStore Version")
|
||||
|
||||
self.tableView.contentInset.bottom = 40
|
||||
|
||||
self.tableView.contentInset.bottom = 20
|
||||
|
||||
self.update()
|
||||
|
||||
@@ -169,7 +148,6 @@ private extension SettingsViewController
|
||||
}
|
||||
|
||||
self.backgroundRefreshSwitch.isOn = UserDefaults.standard.isBackgroundRefreshEnabled
|
||||
self.noIdleTimeoutSwitch.isOn = UserDefaults.standard.isIdleTimeoutDisableEnabled
|
||||
|
||||
if self.isViewLoaded
|
||||
{
|
||||
@@ -200,11 +178,11 @@ private extension SettingsViewController
|
||||
case .patreon:
|
||||
if isHeader
|
||||
{
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("SUPPORT US", comment: "")
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("PATREON", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Support the SideStore Team by following our socials or becoming a patron!", comment: "")
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Support the SideStore Team by becoming a patron!", comment: "")
|
||||
}
|
||||
|
||||
case .account:
|
||||
@@ -221,7 +199,7 @@ private extension SettingsViewController
|
||||
}
|
||||
else
|
||||
{
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Enable Background Refresh to automatically refresh apps in the background when connected to Wi-Fi. \n\nDisable the Idle Timeout toggle to allow SideStore to not let your device go to sleep during a refresh or install of any apps.", comment: "")
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("Enable Background Refresh to automatically refresh apps in the background when connected to Wi-Fi.", comment: "")
|
||||
}
|
||||
|
||||
case .instructions:
|
||||
@@ -302,11 +280,6 @@ private extension SettingsViewController
|
||||
UserDefaults.standard.isBackgroundRefreshEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleNoIdleTimeoutEnabled(_ sender: UISwitch)
|
||||
{
|
||||
UserDefaults.standard.isIdleTimeoutDisableEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@available(iOS 14, *)
|
||||
@IBAction func addRefreshAppsShortcut()
|
||||
{
|
||||
@@ -318,39 +291,6 @@ private extension SettingsViewController
|
||||
self.present(viewController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func clearCache()
|
||||
{
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to clear SideStore's cache?", comment: ""),
|
||||
message: NSLocalizedString("This will remove all temporary files as well as backups for uninstalled apps.", comment: ""),
|
||||
preferredStyle: .actionSheet)
|
||||
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style) { [weak self] _ in
|
||||
self?.tableView.indexPathForSelectedRow.map { self?.tableView.deselectRow(at: $0, animated: true) }
|
||||
})
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Clear Cache", comment: ""), style: .destructive) { [weak self] _ in
|
||||
AppManager.shared.clearAppCache { result in
|
||||
DispatchQueue.main.async {
|
||||
self?.tableView.indexPathForSelectedRow.map { self?.tableView.deselectRow(at: $0, animated: true) }
|
||||
|
||||
switch result
|
||||
{
|
||||
case .success: break
|
||||
case .failure(let error):
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Unable to Clear Cache", comment: ""), message: error.localizedDescription, preferredStyle: .alert)
|
||||
alertController.addAction(.ok)
|
||||
self?.present(alertController, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if let popoverController = alertController.popoverPresentationController {
|
||||
popoverController.sourceView = self.view
|
||||
popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
|
||||
}
|
||||
|
||||
self.present(alertController, animated: true)
|
||||
}
|
||||
|
||||
@IBAction func handleDebugModeGesture(_ gestureRecognizer: UISwipeGestureRecognizer)
|
||||
{
|
||||
self.debugGestureCounter += 1
|
||||
@@ -437,26 +377,15 @@ extension SettingsViewController
|
||||
{
|
||||
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
||||
|
||||
// if #available(iOS 14, *) {}
|
||||
// else if let cell = cell as? InsetGroupTableViewCell,
|
||||
// indexPath.section == Section.appRefresh.rawValue,
|
||||
// indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||
// {
|
||||
// // Only one row is visible pre-iOS 14.
|
||||
// cell.style = .single
|
||||
// }
|
||||
|
||||
if AppRefreshRow.AllCases().count == 1
|
||||
if #available(iOS 14, *) {}
|
||||
else if let cell = cell as? InsetGroupTableViewCell,
|
||||
indexPath.section == Section.appRefresh.rawValue,
|
||||
indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||
{
|
||||
if let cell = cell as? InsetGroupTableViewCell,
|
||||
indexPath.section == Section.appRefresh.rawValue,
|
||||
indexPath.row == AppRefreshRow.backgroundRefresh.rawValue
|
||||
{
|
||||
cell.style = .single
|
||||
}
|
||||
// Only one row is visible pre-iOS 14.
|
||||
cell.style = .single
|
||||
}
|
||||
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
@@ -536,13 +465,11 @@ extension SettingsViewController
|
||||
switch row
|
||||
{
|
||||
case .backgroundRefresh: break
|
||||
case .noIdleTimeout: break
|
||||
case .addToSiri:
|
||||
guard #available(iOS 14, *) else { return }
|
||||
self.addRefreshAppsShortcut()
|
||||
}
|
||||
|
||||
|
||||
case .credits:
|
||||
let row = CreditsRow.allCases[indexPath.row]
|
||||
switch row
|
||||
@@ -580,9 +507,6 @@ extension SettingsViewController
|
||||
let toastView = ToastView(text: NSLocalizedString("Cannot Send Mail", comment: ""), detailText: nil)
|
||||
toastView.show(in: self)
|
||||
}
|
||||
|
||||
case .clearCache: self.clearCache()
|
||||
|
||||
case .resetPairingFile:
|
||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||
let fm = FileManager.default
|
||||
@@ -594,12 +518,11 @@ extension SettingsViewController
|
||||
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Delete and Reset", comment: ""), style: .destructive){ _ in
|
||||
if fm.fileExists(atPath: documentsPath.path), let contents = try? String(contentsOf: documentsPath), !contents.isEmpty {
|
||||
UserDefaults.standard.isPairingReset = true
|
||||
try? fm.removeItem(atPath: documentsPath.path)
|
||||
NSLog("Pairing File Reseted")
|
||||
}
|
||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||
let dialogMessage = UIAlertController(title: NSLocalizedString("Pairing File Reset", comment: ""), message: NSLocalizedString("Please restart SideStore", comment: ""), preferredStyle: .alert)
|
||||
let dialogMessage = UIAlertController(title: NSLocalizedString("Pairing File Reseted", comment: ""), message: NSLocalizedString("Please restart SideStore", comment: ""), preferredStyle: .alert)
|
||||
self.present(dialogMessage, animated: true, completion: nil)
|
||||
})
|
||||
alertController.addAction(.cancel)
|
||||
@@ -636,7 +559,6 @@ extension SettingsViewController
|
||||
ELOG("UIApplication.openSettingsURLString invalid")
|
||||
}
|
||||
case .refreshAttempts, .errorLog: break
|
||||
|
||||
}
|
||||
|
||||
default: break
|
||||
|
||||
@@ -197,7 +197,7 @@ private extension SourcesViewController
|
||||
{
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Add Source", comment: ""), message: nil, preferredStyle: .alert)
|
||||
alertController.addTextField { (textField) in
|
||||
textField.placeholder = "https://apps.sidestore.io"
|
||||
textField.placeholder = "https://apps.altstore.io"
|
||||
textField.textContentType = .URL
|
||||
}
|
||||
alertController.addAction(.cancel)
|
||||
@@ -545,19 +545,19 @@ extension SourcesViewController: UICollectionViewDelegateFlowLayout
|
||||
footerView.textView.delegate = self
|
||||
|
||||
let attributedText = NSMutableAttributedString(
|
||||
string: NSLocalizedString("SideStore has reviewed these sources to make sure they meet our safety standards.", comment: ""),
|
||||
string: NSLocalizedString("SideStore has reviewed these sources to make sure they meet our safety standards.\n\nSupport for untrusted sources is currently in beta, but you can help test them out by", comment: ""),
|
||||
attributes: [.font: font, .foregroundColor: UIColor.gray]
|
||||
)
|
||||
//attributedText.mutableString.append(" ")
|
||||
attributedText.mutableString.append(" ")
|
||||
|
||||
//let boldedFont = UIFont(descriptor: font.fontDescriptor.withSymbolicTraits(.traitBold)!, size: font.pointSize)
|
||||
//let openPatreonURL = URL(string: "https://SideStore.io/")!
|
||||
let boldedFont = UIFont(descriptor: font.fontDescriptor.withSymbolicTraits(.traitBold)!, size: font.pointSize)
|
||||
let openPatreonURL = URL(string: "https://SideStore.io/patreon")!
|
||||
|
||||
// let joinPatreonText = NSAttributedString(
|
||||
// string: NSLocalizedString("", comment: ""),
|
||||
// attributes: [.font: boldedFont, .link: openPatreonURL, .underlineColor: UIColor.clear]
|
||||
//)
|
||||
//attributedText.append(joinPatreonText)
|
||||
let joinPatreonText = NSAttributedString(
|
||||
string: NSLocalizedString("joining our Patreon.", comment: ""),
|
||||
attributes: [.font: boldedFont, .link: openPatreonURL, .underlineColor: UIColor.clear]
|
||||
)
|
||||
attributedText.append(joinPatreonText)
|
||||
|
||||
footerView.textView.attributedText = attributedText
|
||||
footerView.textView.textAlignment = .natural
|
||||
|
||||
61
AltStore/View Components/AppIconView.swift
Normal file
61
AltStore/View Components/AppIconView.swift
Normal file
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// SwiftUIView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 18.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AsyncImage
|
||||
|
||||
struct AppIconView: View {
|
||||
let iconUrl: URL?
|
||||
var size: CGFloat = 64
|
||||
var cornerRadius: CGFloat {
|
||||
size * 0.234
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if let iconUrl {
|
||||
AsyncImage(url: iconUrl) { image in
|
||||
image
|
||||
.resizable()
|
||||
} placeholder: {
|
||||
Color(UIColor.secondarySystemBackground)
|
||||
}
|
||||
.frame(width: size, height: size)
|
||||
.clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AppIconView: Equatable {
|
||||
/// Prevent re-rendering of the view if the parameters didn't change
|
||||
static func == (lhs: AppIconView, rhs: AppIconView) -> Bool {
|
||||
lhs.iconUrl == rhs.iconUrl && lhs.cornerRadius == rhs.cornerRadius
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
struct AppIconView_Previews: PreviewProvider {
|
||||
|
||||
static let context = DatabaseManager.shared.viewContext
|
||||
static let app = StoreApp.makeAltStoreApp(in: context)
|
||||
|
||||
static var previews: some View {
|
||||
HStack {
|
||||
AppIconView(iconUrl: app.iconURL)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(app.name)
|
||||
.bold()
|
||||
Text(app.developerName)
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
144
AltStore/View Components/AppPillButton.swift
Normal file
144
AltStore/View Components/AppPillButton.swift
Normal file
@@ -0,0 +1,144 @@
|
||||
//
|
||||
// AppPillButton.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 20.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AltStoreCore
|
||||
|
||||
struct AppPillButton: View {
|
||||
|
||||
@ObservedObject
|
||||
var appManager = AppManager.shared.publisher
|
||||
|
||||
let app: AppProtocol
|
||||
var showRemainingDays = false
|
||||
|
||||
var storeApp: StoreApp? {
|
||||
(app as? StoreApp) ?? (app as? InstalledApp)?.storeApp
|
||||
}
|
||||
|
||||
var installedApp: InstalledApp? {
|
||||
(app as? InstalledApp) ?? (app as? StoreApp)?.installedApp
|
||||
}
|
||||
|
||||
var progress: Progress? {
|
||||
appManager.refreshProgress[app.bundleIdentifier] ?? appManager.installationProgress[app.bundleIdentifier]
|
||||
}
|
||||
// let progress = {
|
||||
// let progress = Progress(totalUnitCount: 100)
|
||||
// progress.completedUnitCount = 20
|
||||
// return progress
|
||||
// }()
|
||||
|
||||
var buttonText: String {
|
||||
// guard progress == nil else {
|
||||
// return ""
|
||||
// }
|
||||
|
||||
if let installedApp {
|
||||
if self.showRemainingDays {
|
||||
return DateFormatterHelper.string(forExpirationDate: installedApp.expirationDate)
|
||||
}
|
||||
|
||||
return L10n.AppPillButton.open
|
||||
}
|
||||
|
||||
return L10n.AppPillButton.free
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
SwiftUI.Button(action: handleButton) {
|
||||
Text(buttonText.uppercased())
|
||||
.bold()
|
||||
}
|
||||
.buttonStyle(PillButtonStyle(tintColor: storeApp?.tintColor ?? .black, progress: progress))
|
||||
}
|
||||
|
||||
func handleButton() {
|
||||
if let installedApp {
|
||||
if showRemainingDays {
|
||||
self.refreshApp(installedApp)
|
||||
} else {
|
||||
self.openApp(installedApp)
|
||||
}
|
||||
} else if let storeApp {
|
||||
self.installApp(storeApp)
|
||||
}
|
||||
}
|
||||
|
||||
func openApp(_ installedApp: InstalledApp) {
|
||||
UIApplication.shared.open(installedApp.openAppURL)
|
||||
}
|
||||
|
||||
func refreshApp(_ installedApp: InstalledApp) {
|
||||
AppManager.shared.refresh([installedApp], presentingViewController: nil)
|
||||
}
|
||||
|
||||
func installApp(_ storeApp: StoreApp) {
|
||||
let previousProgress = AppManager.shared.installationProgress(for: storeApp)
|
||||
guard previousProgress == nil else {
|
||||
previousProgress?.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
let _ = AppManager.shared.install(storeApp, presentingViewController: UIApplication.shared.keyWindow?.rootViewController) { result in
|
||||
|
||||
switch result {
|
||||
case let .success(installedApp):
|
||||
print("Installed app: \(installedApp.bundleIdentifier)")
|
||||
|
||||
case let .failure(error):
|
||||
print("Failed to install app: \(error.localizedDescription)")
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
AppManager.shared.installationProgress(for: storeApp)?.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AppPillButton_Previews: PreviewProvider {
|
||||
|
||||
static let context = DatabaseManager.shared.viewContext
|
||||
static let app = StoreApp.makeAltStoreApp(in: context)
|
||||
static let installedApp = InstalledApp.fetchAltStore(in: context)
|
||||
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
self.preview(for: app)
|
||||
|
||||
self.preview(for: installedApp!)
|
||||
|
||||
self.preview(for: installedApp!, showRemainingDays: true)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
static func preview(for app: AppProtocol, showRemainingDays: Bool = false) -> some View {
|
||||
HintView(backgroundColor: Color(UIColor.secondarySystemBackground)) {
|
||||
HStack {
|
||||
AppIconView(iconUrl: self.app.iconURL)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(app is StoreApp ? "Store App" : "Installed App")
|
||||
.bold()
|
||||
Text(
|
||||
app is StoreApp ?
|
||||
"Can be installed" :
|
||||
showRemainingDays ? "Can be refreshed" : "Can be opened"
|
||||
)
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
AppPillButton(app: app, showRemainingDays: showRemainingDays)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
AltStore/View Components/AppRowView.swift
Normal file
53
AltStore/View Components/AppRowView.swift
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// AppRowView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 18.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AltStoreCore
|
||||
|
||||
struct AppRowView: View {
|
||||
let app: AppProtocol
|
||||
|
||||
var storeApp: StoreApp? {
|
||||
(app as? StoreApp) ?? (app as? InstalledApp)?.storeApp
|
||||
}
|
||||
|
||||
var showRemainingDays: Bool = false
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .center, spacing: 12) {
|
||||
AppIconView(iconUrl: storeApp?.iconURL)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(app.name)
|
||||
.bold()
|
||||
|
||||
Text(storeApp?.developerName ?? L10n.AppRowView.sideloaded)
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
RatingStars(rating: 4)
|
||||
.frame(height: 12)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer()
|
||||
|
||||
AppPillButton(app: app, showRemainingDays: showRemainingDays)
|
||||
}
|
||||
.padding()
|
||||
.tintedBackground(Color(storeApp?.tintColor ?? UIColor(Color.accentColor)))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24, style: .circular))
|
||||
}
|
||||
}
|
||||
|
||||
//struct AppRowView_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// AppRowView()
|
||||
// }
|
||||
//}
|
||||
55
AltStore/View Components/AppScreenshot.swift
Normal file
55
AltStore/View Components/AppScreenshot.swift
Normal file
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// AppScreenshot.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 20.12.22.
|
||||
// Copyright © 2022 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import AsyncImage
|
||||
|
||||
struct AppScreenshot: View {
|
||||
let url: URL
|
||||
var aspectRatio: CGFloat = 9/16
|
||||
|
||||
static let processor = Self.ScreenshotProcessor()
|
||||
|
||||
var body: some View {
|
||||
AsyncImage(url: self.url, processor: Self.processor) { image in
|
||||
image
|
||||
.resizable()
|
||||
} placeholder: {
|
||||
Rectangle()
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.aspectRatio(self.aspectRatio, contentMode: .fit)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
|
||||
extension AppScreenshot {
|
||||
class ScreenshotProcessor: ImageProcessor {
|
||||
func process(image: UIImage) -> UIImage {
|
||||
guard let cgImage = image.cgImage, image.size.width > image.size.height else { return image }
|
||||
|
||||
let rotatedImage = UIImage(cgImage: cgImage, scale: image.scale, orientation: .right)
|
||||
return rotatedImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
struct AppScreenshot_Previews: PreviewProvider {
|
||||
|
||||
static let context = DatabaseManager.shared.viewContext
|
||||
static let app = StoreApp.makeAltStoreApp(in: context)
|
||||
|
||||
static var previews: some View {
|
||||
AppScreenshot(url: app.screenshotURLs[0])
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
42
AltStore/View Components/HintView.swift
Normal file
42
AltStore/View Components/HintView.swift
Normal file
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// HintView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 15.01.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct HintView<Content: View>: View {
|
||||
|
||||
var backgroundColor: Color = Color(.tertiarySystemBackground)
|
||||
|
||||
@ViewBuilder
|
||||
let content: () -> Content
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
self.content()
|
||||
}
|
||||
.padding()
|
||||
.background(self.backgroundColor)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
}
|
||||
}
|
||||
|
||||
struct HintView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ZStack {
|
||||
Color(.secondarySystemBackground).edgesIgnoringSafeArea(.all)
|
||||
|
||||
HintView {
|
||||
Text("Hint Title")
|
||||
.bold()
|
||||
|
||||
Text("This hint view can be used to tell the user something about how SideStore works.")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
AltStore/View Components/ModalNavigationLink.swift
Normal file
45
AltStore/View Components/ModalNavigationLink.swift
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// ModalNavigationLink.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 03.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ModalNavigationLink<Label: View, Modal: View>: View {
|
||||
let modal: () -> Modal
|
||||
let label: () -> Label
|
||||
|
||||
@State var isPresentingModal: Bool = false
|
||||
|
||||
init(@ViewBuilder modal: @escaping () -> Modal, @ViewBuilder label: @escaping () -> Label) {
|
||||
self.modal = modal
|
||||
self.label = label
|
||||
}
|
||||
|
||||
init(_ title: String, @ViewBuilder modal: @escaping () -> Modal) where Label == Text {
|
||||
self.modal = modal
|
||||
self.label = { Text(title) }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
SwiftUI.Button {
|
||||
self.isPresentingModal = true
|
||||
} label: {
|
||||
self.label()
|
||||
}
|
||||
.sheet(isPresented: self.$isPresentingModal) {
|
||||
self.modal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ModalNavigationLink_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ModalNavigationLink("Present Modal") {
|
||||
Text("Modal")
|
||||
}
|
||||
}
|
||||
}
|
||||
47
AltStore/View Components/ObservableScrollView.swift
Normal file
47
AltStore/View Components/ObservableScrollView.swift
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// ObservableScrollView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 20.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ObservableScrollView<Content: View>: View {
|
||||
@Namespace var scrollViewNamespace
|
||||
|
||||
@Binding var scrollOffset: CGFloat
|
||||
|
||||
let content: (ScrollViewProxy) -> Content
|
||||
|
||||
init(scrollOffset: Binding<CGFloat>, @ViewBuilder content: @escaping (ScrollViewProxy) -> Content) {
|
||||
self._scrollOffset = scrollOffset
|
||||
self.content = content
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
ScrollViewReader { proxy in
|
||||
content(proxy)
|
||||
.background(GeometryReader { geoReader in
|
||||
let offset = -geoReader.frame(in: .named(scrollViewNamespace)).minY
|
||||
Color.clear
|
||||
.preference(key: ScrollViewOffsetPreferenceKey.self, value: offset)
|
||||
})
|
||||
}
|
||||
}
|
||||
.coordinateSpace(name: scrollViewNamespace)
|
||||
.onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in
|
||||
scrollOffset = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ScrollViewOffsetPreferenceKey: PreferenceKey {
|
||||
static var defaultValue = CGFloat.zero
|
||||
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||
value += nextValue()
|
||||
}
|
||||
}
|
||||
31
AltStore/View Components/RatingStars.swift
Normal file
31
AltStore/View Components/RatingStars.swift
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// RatingStars.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 18.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
struct RatingStars: View {
|
||||
|
||||
let rating: Int
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
ForEach(0..<5) { i in
|
||||
Image(systemSymbol: i < rating ? .starFill : .star)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RatingStars_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
RatingStars(rating: 4)
|
||||
}
|
||||
}
|
||||
52
AltStore/View Components/RoundedTextField.swift
Normal file
52
AltStore/View Components/RoundedTextField.swift
Normal file
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// RoundedTextField.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 29.11.22.
|
||||
// Copyright © 2022 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RoundedTextField: View {
|
||||
|
||||
let title: String?
|
||||
let placeholder: String
|
||||
@Binding var text: String
|
||||
let isSecure: Bool
|
||||
|
||||
init(title: String?, placeholder: String, text: Binding<String>, isSecure: Bool = false) {
|
||||
self.title = title
|
||||
self.placeholder = placeholder
|
||||
self._text = text
|
||||
self.isSecure = isSecure
|
||||
}
|
||||
|
||||
init(_ placeholder: String, text: Binding<String>, isSecure: Bool = false) {
|
||||
self.init(title: nil, placeholder: placeholder, text: text, isSecure: isSecure)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
if let title {
|
||||
Text(title.uppercased())
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
HStack(alignment: .center) {
|
||||
if isSecure {
|
||||
SecureField(placeholder, text: $text)
|
||||
} else {
|
||||
TextField(placeholder, text: $text)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.foregroundColor(Color(.secondarySystemBackground))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
16
AltStore/View Extensions/EnvironmentValues.swift
Normal file
16
AltStore/View Extensions/EnvironmentValues.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// EnvironmentValues.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 29.11.22.
|
||||
// Copyright © 2022 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
extension EnvironmentValues {
|
||||
var dismiss: () -> Void {
|
||||
{ presentationMode.wrappedValue.dismiss() }
|
||||
}
|
||||
}
|
||||
35
AltStore/View Extensions/Modifiers.swift
Normal file
35
AltStore/View Extensions/Modifiers.swift
Normal file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Modifiers.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 01.12.22.
|
||||
// Copyright © 2022 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
|
||||
@ViewBuilder func `if`<Content: View>(_ condition: Bool, @ViewBuilder transform: (Self) -> Content) -> some View {
|
||||
if condition {
|
||||
transform(self)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ViewBuilder func searchable(text: Binding<String>, placeholder: String) -> some View {
|
||||
if #available(iOS 15.0, *) {
|
||||
self.searchable(text: text, prompt: Text(placeholder))
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func tintedBackground(_ color: Color) -> some View {
|
||||
self
|
||||
.blurBackground(.systemUltraThinMaterial)
|
||||
.background(color.opacity(0.4))
|
||||
}
|
||||
}
|
||||
47
AltStore/View Extensions/Styles/FilledButtonStyle.swift
Normal file
47
AltStore/View Extensions/Styles/FilledButtonStyle.swift
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// FilledButtonStyle.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 29.11.22.
|
||||
// Copyright © 2022 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct FilledButtonStyle: ButtonStyle {
|
||||
var isLoading: Bool = false
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
ZStack {
|
||||
configuration.label
|
||||
.opacity(isLoading ? 0 : 1)
|
||||
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
}
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.foregroundColor(.accentColor)
|
||||
)
|
||||
.opacity(configuration.isPressed || isLoading ? 0.7 : 1)
|
||||
.disabled(isLoading)
|
||||
}
|
||||
}
|
||||
|
||||
struct FilledButtonStyle_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SwiftUI.Button {
|
||||
|
||||
} label: {
|
||||
Label("Test Button", systemImage: "testtube.2")
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// PillButtonProgressViewStyle.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 22.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct PillButtonProgressViewStyle: ProgressViewStyle {
|
||||
let tint: Color
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
ZStack(alignment: .leading) {
|
||||
Capsule(style: .continuous)
|
||||
.foregroundColor(tint.opacity(0.15))
|
||||
|
||||
GeometryReader { proxy in
|
||||
Capsule(style: .continuous)
|
||||
// .frame(width: proxy.size.width * (configuration.fractionCompleted ?? 0.0))
|
||||
.foregroundColor(tint)
|
||||
.offset(x: -proxy.size.width * (1 - (configuration.fractionCompleted ?? 1)))
|
||||
}
|
||||
}
|
||||
.animation(.easeInOut(duration: 0.2))
|
||||
}
|
||||
}
|
||||
59
AltStore/View Extensions/Styles/PillButtonStyle.swift
Normal file
59
AltStore/View Extensions/Styles/PillButtonStyle.swift
Normal file
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// PillButtonStyle.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 18.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct PillButtonStyle: ButtonStyle {
|
||||
|
||||
let tintColor: UIColor
|
||||
var progress: Progress?
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
ZStack {
|
||||
if progress == nil {
|
||||
configuration.label
|
||||
.opacity(configuration.isPressed ? 0.4 : 1.0)
|
||||
} else {
|
||||
ProgressView()
|
||||
.progressViewStyle(DefaultProgressViewStyle())
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 40)
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 6)
|
||||
.background(background)
|
||||
.foregroundColor(self.progress == nil ? .white : Color(tintColor))
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
|
||||
var background: some View {
|
||||
ZStack {
|
||||
if let progress {
|
||||
Color(tintColor).opacity(0.15)
|
||||
|
||||
ProgressView(progress)
|
||||
.progressViewStyle(PillButtonProgressViewStyle(tint: Color(tintColor)))
|
||||
} else {
|
||||
Color(tintColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct PillButtonStyle_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
SwiftUI.Button {
|
||||
|
||||
} label: {
|
||||
Text("Label").bold()
|
||||
}
|
||||
.buttonStyle(PillButtonStyle(tintColor: Asset.accentColor.color))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// ActivityView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 19.05.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
|
||||
struct ActivityView: UIViewControllerRepresentable {
|
||||
let items: [Any]
|
||||
|
||||
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
|
||||
return UIActivityViewController(activityItems: items, applicationActivities: nil)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityView>) {}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// AppStoreProductView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 25.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import StoreKit
|
||||
|
||||
|
||||
struct AppStoreView: UIViewControllerRepresentable {
|
||||
typealias UIViewControllerType = AppStoreProductViewController
|
||||
|
||||
var isVisible: Binding<Bool>
|
||||
let itunesItemId: Int
|
||||
|
||||
func makeUIViewController(context: Context) -> AppStoreProductViewController {
|
||||
AppStoreProductViewController(isVisible: self.isVisible, itunesId: self.itunesItemId)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
|
||||
if self.isVisible.wrappedValue {
|
||||
uiViewController.presentStoreProduct()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AppStoreProductViewController: UIViewController {
|
||||
|
||||
private var isVisible: Binding<Bool>
|
||||
private let itunesId: Int
|
||||
|
||||
init(isVisible: Binding<Bool>, itunesId: Int) {
|
||||
self.isVisible = isVisible
|
||||
self.itunesId = itunesId
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
}
|
||||
|
||||
func presentStoreProduct() {
|
||||
let storeProductViewController = SKStoreProductViewController()
|
||||
storeProductViewController.delegate = self
|
||||
|
||||
let parameters = [SKStoreProductParameterITunesItemIdentifier: self.itunesId]
|
||||
storeProductViewController.loadProduct(withParameters: parameters) { (success, error) -> Void in
|
||||
if let error = error {
|
||||
print("Failed to load App Store product: \(error.localizedDescription)")
|
||||
}
|
||||
guard success else {
|
||||
return
|
||||
}
|
||||
|
||||
self.present(storeProductViewController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SKStoreProductViewControllerDelegate
|
||||
|
||||
extension AppStoreProductViewController: SKStoreProductViewControllerDelegate {
|
||||
|
||||
func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) {
|
||||
DispatchQueue.main.async {
|
||||
self.isVisible.wrappedValue = false
|
||||
}
|
||||
// viewController.presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// DocumentPicker.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 20.12.22.
|
||||
// Copyright © 2022 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
|
||||
struct DocumentPicker: UIViewControllerRepresentable {
|
||||
internal class Coordinator: NSObject {
|
||||
var parent: DocumentPicker
|
||||
|
||||
init(_ parent: DocumentPicker) {
|
||||
self.parent = parent
|
||||
}
|
||||
}
|
||||
|
||||
@Binding var selectedUrl: URL?
|
||||
let supportedTypes: [String]
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(self)
|
||||
}
|
||||
|
||||
func makeUIViewController(context: Context) -> some UIViewController {
|
||||
|
||||
let documentPickerViewController = UIDocumentPickerViewController(documentTypes: supportedTypes, in: .import)
|
||||
documentPickerViewController.delegate = context.coordinator
|
||||
return documentPickerViewController
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
|
||||
}
|
||||
|
||||
extension DocumentPicker.Coordinator: UIDocumentPickerDelegate {
|
||||
|
||||
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
|
||||
self.parent.selectedUrl = nil
|
||||
}
|
||||
|
||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
||||
|
||||
guard let firstURL = urls.first else {
|
||||
return
|
||||
}
|
||||
|
||||
self.parent.selectedUrl = firstURL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// FilePreviewView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 03.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import QuickLook
|
||||
|
||||
struct FilePreviewView: UIViewControllerRepresentable {
|
||||
let urls: [URL]
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(urls: self.urls)
|
||||
}
|
||||
|
||||
func makeUIViewController(context: Context) -> some UIViewController {
|
||||
let previewController = QLPreviewController()
|
||||
previewController.dataSource = context.coordinator
|
||||
return UINavigationController(rootViewController: previewController)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
|
||||
context.coordinator.urls = self.urls
|
||||
}
|
||||
}
|
||||
|
||||
extension FilePreviewView {
|
||||
|
||||
class Coordinator: QLPreviewControllerDataSource {
|
||||
var urls: [URL]
|
||||
|
||||
init(urls: [URL]) {
|
||||
self.urls = urls
|
||||
}
|
||||
|
||||
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
|
||||
urls.count
|
||||
}
|
||||
|
||||
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
|
||||
urls[index] as QLPreviewItem
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// MailComposeView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 04.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import MessageUI
|
||||
|
||||
struct MailComposeView: UIViewControllerRepresentable {
|
||||
typealias ActionHandler = () -> Void
|
||||
typealias ErrorHandler = (Error) -> Void
|
||||
|
||||
static var canSendMail: Bool {
|
||||
MFMailComposeViewController.canSendMail()
|
||||
}
|
||||
|
||||
let recipients: [String]
|
||||
let subject: String
|
||||
var body: String? = nil
|
||||
|
||||
var onMailSent: ActionHandler? = nil
|
||||
var onError: ErrorHandler? = nil
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(mailSentHandler: self.onMailSent, errorHandler: self.onError)
|
||||
}
|
||||
|
||||
func makeUIViewController(context: Context) -> some UIViewController {
|
||||
let mailViewController = MFMailComposeViewController()
|
||||
mailViewController.mailComposeDelegate = context.coordinator
|
||||
mailViewController.setToRecipients(self.recipients)
|
||||
mailViewController.setSubject(self.subject)
|
||||
|
||||
if let body {
|
||||
mailViewController.setMessageBody(body, isHTML: false)
|
||||
}
|
||||
|
||||
return mailViewController
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension MailComposeView {
|
||||
class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
|
||||
|
||||
let mailSentHandler: ActionHandler?
|
||||
let errorHandler: ErrorHandler?
|
||||
|
||||
init(mailSentHandler: ActionHandler?, errorHandler: ErrorHandler?) {
|
||||
self.mailSentHandler = mailSentHandler
|
||||
self.errorHandler = errorHandler
|
||||
super.init()
|
||||
}
|
||||
|
||||
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||
if result == .sent, let mailSentHandler {
|
||||
mailSentHandler()
|
||||
} else if result == .failed, let errorHandler, let error {
|
||||
errorHandler(error)
|
||||
}
|
||||
|
||||
controller.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// SafariView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 18.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SafariServices
|
||||
|
||||
struct SafariView: UIViewControllerRepresentable {
|
||||
let url: URL
|
||||
|
||||
func makeUIViewController(context: Context) -> some UIViewController {
|
||||
SFSafariViewController(url: url)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// SiriShortcutSetupView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 21.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
import UIKit
|
||||
import Intents
|
||||
import IntentsUI
|
||||
|
||||
struct SiriShortcutSetupView: UIViewControllerRepresentable {
|
||||
|
||||
let shortcut: INShortcut
|
||||
|
||||
func makeUIViewController(context: Context) -> some UIViewController {
|
||||
let viewController = INUIAddVoiceShortcutViewController(shortcut: shortcut)
|
||||
viewController.delegate = context.coordinator
|
||||
viewController.modalPresentationStyle = .formSheet
|
||||
return viewController
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(shortcut: shortcut)
|
||||
}
|
||||
|
||||
class Coordinator: NSObject {
|
||||
|
||||
let shortcut: INShortcut
|
||||
|
||||
init(shortcut: INShortcut) {
|
||||
self.shortcut = shortcut
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SiriShortcutSetupView.Coordinator: INUIAddVoiceShortcutViewControllerDelegate {
|
||||
func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?) {
|
||||
|
||||
// TODO: Handle errors
|
||||
controller.dismiss(animated: true)
|
||||
}
|
||||
|
||||
func addVoiceShortcutViewControllerDidCancel(_ controller: INUIAddVoiceShortcutViewController) {
|
||||
controller.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// VisualEffectView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 01.12.22.
|
||||
// Copyright © 2022 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct VisualEffectView: UIViewRepresentable {
|
||||
let blurStyle: UIBlurEffect.Style
|
||||
|
||||
func makeUIView(context: Context) -> some UIView {
|
||||
UIVisualEffectView(effect: UIBlurEffect(style: blurStyle))
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UIViewType, context: Context) { }
|
||||
}
|
||||
|
||||
|
||||
extension View {
|
||||
@ViewBuilder
|
||||
func blurBackground(_ style: UIBlurEffect.Style) -> some View {
|
||||
self
|
||||
.background(VisualEffectView(blurStyle: style))
|
||||
}
|
||||
}
|
||||
454
AltStore/Views/App Detail/AppDetailView.swift
Normal file
454
AltStore/Views/App Detail/AppDetailView.swift
Normal file
@@ -0,0 +1,454 @@
|
||||
//
|
||||
// AppDetailView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 18.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AsyncImage
|
||||
import ExpandableText
|
||||
import SFSafeSymbols
|
||||
import AltStoreCore
|
||||
|
||||
struct AppDetailView: View {
|
||||
|
||||
let storeApp: StoreApp
|
||||
|
||||
let byteCountFormatter: ByteCountFormatter = {
|
||||
let formatter = ByteCountFormatter()
|
||||
return formatter
|
||||
}()
|
||||
|
||||
@State var scrollOffset: CGFloat = .zero
|
||||
let maxContentCornerRadius: CGFloat = 24
|
||||
let headerViewHeight: CGFloat = 140
|
||||
let permissionColumns = 4
|
||||
|
||||
var headerBlurRadius: CGFloat {
|
||||
min(20, max(0, 20 - (scrollOffset / -150) * 20))
|
||||
}
|
||||
var isHeaderViewVisible: Bool {
|
||||
scrollOffset < headerViewHeight + 12
|
||||
}
|
||||
var contentCornerRadius: CGFloat {
|
||||
max(CGFloat.zero, min(maxContentCornerRadius, maxContentCornerRadius * (1 - self.scrollOffset / self.headerViewHeight)))
|
||||
}
|
||||
|
||||
var canRateApp: Bool {
|
||||
self.storeApp.installedApp != nil
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
ObservableScrollView(scrollOffset: $scrollOffset) { proxy in
|
||||
LazyVStack {
|
||||
headerView
|
||||
.frame(height: headerViewHeight)
|
||||
|
||||
contentView
|
||||
}
|
||||
}
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
AppPillButton(app: storeApp)
|
||||
.disabled(isHeaderViewVisible)
|
||||
.offset(y: isHeaderViewVisible ? 12 : 0)
|
||||
.opacity(isHeaderViewVisible ? 0 : 1)
|
||||
.animation(.easeInOut(duration: 0.2), value: isHeaderViewVisible)
|
||||
}
|
||||
|
||||
ToolbarItemGroup(placement: .principal) {
|
||||
HStack {
|
||||
Spacer()
|
||||
AppIconView(iconUrl: storeApp.iconURL, size: 24)
|
||||
Text(storeApp.name)
|
||||
.bold()
|
||||
Spacer()
|
||||
}
|
||||
.offset(y: isHeaderViewVisible ? 12 : 0)
|
||||
.opacity(isHeaderViewVisible ? 0 : 1)
|
||||
.animation(.easeInOut(duration: 0.2), value: isHeaderViewVisible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var headerView: some View {
|
||||
ZStack(alignment: .center) {
|
||||
GeometryReader { proxy in
|
||||
AppIconView(iconUrl: storeApp.iconURL, size: proxy.frame(in: .global).width)
|
||||
.blur(radius: headerBlurRadius)
|
||||
.offset(y: min(0, scrollOffset))
|
||||
}
|
||||
.padding()
|
||||
|
||||
AppRowView(app: storeApp)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
var contentView: some View {
|
||||
VStack(alignment: .leading, spacing: 24) {
|
||||
VStack(alignment: .leading, spacing: 32) {
|
||||
if storeApp.isFromOfficialSource {
|
||||
officialAppBadge
|
||||
} else if storeApp.isFromTrustedSource {
|
||||
trustedAppBadge
|
||||
}
|
||||
|
||||
if let subtitle = storeApp.subtitle {
|
||||
VStack {
|
||||
if #available(iOS 15.0, *) {
|
||||
Image(systemSymbol: .quoteOpening)
|
||||
.foregroundColor(.secondary.opacity(0.5))
|
||||
.imageScale(.large)
|
||||
.transformEffect(CGAffineTransform(a: 1, b: 0, c: -0.3, d: 1, tx: 0, ty: 0))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.offset(x: 30)
|
||||
}
|
||||
|
||||
Text(subtitle)
|
||||
.bold()
|
||||
.italic()
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
Image(systemSymbol: .quoteClosing)
|
||||
.foregroundColor(.secondary.opacity(0.5))
|
||||
.imageScale(.large)
|
||||
.transformEffect(CGAffineTransform(a: 1, b: 0, c: -0.3, d: 1, tx: 0, ty: 0))
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
.offset(x: -30)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
if !storeApp.screenshotURLs.isEmpty {
|
||||
// Equatable: Only reload the view if the screenshots change.
|
||||
// This prevents unnecessary redraws on scroll.
|
||||
AppScreenshotsScrollView(urls: storeApp.screenshotURLs)
|
||||
.equatable()
|
||||
} else {
|
||||
VStack() {
|
||||
Text(L10n.AppDetailView.noScreenshots)
|
||||
.italic()
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
ExpandableText(text: storeApp.localizedDescription)
|
||||
.lineLimit(6)
|
||||
.expandButton(TextSet(text: L10n.AppDetailView.more, font: .callout, color: .accentColor))
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
|
||||
VStack(spacing: 24) {
|
||||
Divider()
|
||||
|
||||
currentVersionView
|
||||
|
||||
Divider()
|
||||
|
||||
ratingsView
|
||||
|
||||
Divider()
|
||||
|
||||
permissionsView
|
||||
|
||||
Divider()
|
||||
|
||||
informationView
|
||||
|
||||
if !(storeApp.isFromOfficialSource || storeApp.isFromTrustedSource) {
|
||||
Divider()
|
||||
|
||||
reportButton
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.padding(.vertical)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: contentCornerRadius)
|
||||
.foregroundColor(Color(UIColor.systemBackground))
|
||||
.shadow(radius: isHeaderViewVisible ? 12 : 0)
|
||||
)
|
||||
}
|
||||
|
||||
var officialAppBadge: some View {
|
||||
HintView(backgroundColor: Color(UIColor.secondarySystemBackground)) {
|
||||
HStack {
|
||||
Spacer()
|
||||
Image(systemSymbol: .checkmarkSealFill)
|
||||
Text(L10n.AppDetailView.Badge.official)
|
||||
Spacer()
|
||||
}
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
var trustedAppBadge: some View {
|
||||
HintView(backgroundColor: Color(UIColor.secondarySystemBackground)) {
|
||||
HStack {
|
||||
Spacer()
|
||||
Image(systemSymbol: .shieldLefthalfFill)
|
||||
Text(L10n.AppDetailView.Badge.trusted)
|
||||
Spacer()
|
||||
}
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
var currentVersionView: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
VStack {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Text(L10n.AppDetailView.whatsNew)
|
||||
.bold()
|
||||
.font(.title3)
|
||||
|
||||
Spacer()
|
||||
|
||||
NavigationLink {
|
||||
AppVersionHistoryView(storeApp: self.storeApp)
|
||||
} label: {
|
||||
Text(L10n.AppDetailView.WhatsNew.versionHistory)
|
||||
}
|
||||
}
|
||||
|
||||
if let latestVersion = storeApp.latestVersion {
|
||||
HStack {
|
||||
Text(L10n.AppDetailView.version(latestVersion.version))
|
||||
Spacer()
|
||||
Text(DateFormatterHelper.string(forRelativeDate: latestVersion.date))
|
||||
}
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
if let versionDescription = storeApp.versionDescription {
|
||||
ExpandableText(text: versionDescription)
|
||||
.lineLimit(5)
|
||||
.expandButton(TextSet(text: L10n.AppDetailView.more, font: .callout, color: .accentColor))
|
||||
} else {
|
||||
Text(L10n.AppDetailView.noVersionInformation)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
|
||||
if true {
|
||||
SwiftUI.Button {
|
||||
UIApplication.shared.open(URL(string: "https://github.com/SideStore/SideStore")!) { _ in }
|
||||
} label: {
|
||||
HStack {
|
||||
Text(L10n.AppDetailView.WhatsNew.showOnGithub)
|
||||
Image(systemSymbol: .arrowUpForwardSquare)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ratingsView: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Text(L10n.AppDetailView.whatsNew)
|
||||
.bold()
|
||||
.font(.title3)
|
||||
|
||||
Spacer()
|
||||
|
||||
NavigationLink {
|
||||
AppVersionHistoryView(storeApp: self.storeApp)
|
||||
} label: {
|
||||
Text(L10n.AppDetailView.Reviews.seeAll)
|
||||
}
|
||||
}
|
||||
|
||||
HStack(spacing: 40) {
|
||||
VStack {
|
||||
Text("3.0")
|
||||
.font(.system(size: 48, weight: .bold, design: .rounded))
|
||||
.opacity(0.8)
|
||||
Text(L10n.AppDetailView.Reviews.outOf(5))
|
||||
.bold()
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
VStack(alignment: .trailing) {
|
||||
LazyVGrid(columns: [GridItem(.fixed(48), alignment: .trailing), GridItem(.flexible())], spacing: 2) {
|
||||
ForEach(Array(1...5).reversed(), id: \.self) { rating in
|
||||
HStack(spacing: 2) {
|
||||
ForEach(0..<rating) { _ in
|
||||
Image(systemSymbol: .starFill)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 8)
|
||||
}
|
||||
}
|
||||
|
||||
ProgressView(value: 0.5)
|
||||
.frame(maxWidth: .infinity)
|
||||
.progressViewStyle(LinearProgressViewStyle(tint: .secondary))
|
||||
}
|
||||
}
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Text(L10n.AppDetailView.Reviews.ratings(5))
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
TabView {
|
||||
ForEach(0..<5) { i in
|
||||
HintView(backgroundColor: Color(UIColor.secondarySystemBackground)) {
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack {
|
||||
Text("Review \(i + 1)")
|
||||
.bold()
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(DateFormatterHelper.string(forRelativeDate: Date().addingTimeInterval(-60*60)))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
RatingStars(rating: 5 - i)
|
||||
.frame(height: 12)
|
||||
.foregroundColor(.yellow)
|
||||
}
|
||||
|
||||
ExpandableText(text: "Long review text content here.\nMultiple lines.\nAt least three are shown.\nBut are there more?")
|
||||
.lineLimit(3)
|
||||
.expandButton(TextSet(text: L10n.AppDetailView.more, font: .callout, color: .accentColor))
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
}
|
||||
.tag(i)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
}
|
||||
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
|
||||
.frame(height: 150)
|
||||
.padding(.horizontal, -16)
|
||||
|
||||
if self.canRateApp {
|
||||
ModalNavigationLink {
|
||||
NavigationView {
|
||||
WriteAppReviewView(storeApp: self.storeApp)
|
||||
}
|
||||
} label: {
|
||||
Label("Write a Review", systemSymbol: .squareAndPencil)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var permissionsView: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(L10n.AppDetailView.permissions)
|
||||
.bold()
|
||||
.font(.title3)
|
||||
|
||||
if storeApp.permissions.isEmpty {
|
||||
Text(L10n.AppDetailView.noPermissions)
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
AppPermissionsGrid(permissions: storeApp.permissions)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
var informationData: [(title: String, content: String)] {
|
||||
var data: [(title: String, content: String)] = [
|
||||
(L10n.AppDetailView.Information.source, self.storeApp.source?.name ?? ""),
|
||||
(L10n.AppDetailView.Information.developer, self.storeApp.developerName),
|
||||
// ("Category", self.storeApp.category),
|
||||
]
|
||||
|
||||
if let latestVersion = self.storeApp.latestVersion {
|
||||
data += [
|
||||
(L10n.AppDetailView.Information.size, self.byteCountFormatter.string(fromByteCount: latestVersion.size)),
|
||||
(L10n.AppDetailView.Information.latestVersion, self.storeApp.latestVersion?.version ?? ""),
|
||||
]
|
||||
|
||||
let iOSVersion = ProcessInfo.processInfo.operatingSystemVersion
|
||||
let hasCompatibilityInfo = [latestVersion.minOSVersion, latestVersion.maxOSVersion].compactMap({ $0 }).isEmpty == false
|
||||
var compatibility: String = hasCompatibilityInfo ?
|
||||
L10n.AppDetailView.Information.compatibilityCompatible :
|
||||
L10n.AppDetailView.Information.compatibilityUnknown
|
||||
|
||||
if let minOSVersion = latestVersion.minOSVersion, ProcessInfo.processInfo.isOperatingSystemAtLeast(minOSVersion) == false {
|
||||
compatibility = L10n.AppDetailView.Information.compatibilityAtLeast(minOSVersion.stringValue)
|
||||
}
|
||||
|
||||
if let maxOSVersion = latestVersion.maxOSVersion,
|
||||
(!ProcessInfo.processInfo.isOperatingSystemAtLeast(maxOSVersion) || maxOSVersion.stringValue.compare(iOSVersion.stringValue, options: .numeric) == .orderedSame) {
|
||||
compatibility = L10n.AppDetailView.Information.compatibilityOrLower(maxOSVersion.stringValue)
|
||||
}
|
||||
|
||||
data.append((L10n.AppDetailView.Information.compatibility, compatibility))
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
var informationView: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text(L10n.AppDetailView.information)
|
||||
.bold()
|
||||
.font(.title3)
|
||||
|
||||
LazyVGrid(columns: [GridItem(.flexible(), alignment: .leading), GridItem(.flexible(), alignment: .trailing)], spacing: 8) {
|
||||
ForEach(informationData, id: \.title) { title, content in
|
||||
Text(title)
|
||||
.foregroundColor(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
Text(content)
|
||||
.multilineTextAlignment(.trailing)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var reportButton: some View {
|
||||
SwiftUI.Button {
|
||||
|
||||
} label: {
|
||||
Label("Report this App", systemSymbol: .exclamationmarkBubble)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct AppDetailView_Previews: PreviewProvider {
|
||||
|
||||
static let context = DatabaseManager.shared.viewContext
|
||||
static let app = StoreApp.makeAltStoreApp(in: context)
|
||||
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
AppDetailView(storeApp: app)
|
||||
}
|
||||
}
|
||||
}
|
||||
56
AltStore/Views/App Detail/AppPermissionsGrid.swift
Normal file
56
AltStore/Views/App Detail/AppPermissionsGrid.swift
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// AppPermissionsGrid.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 27.11.22.
|
||||
// Copyright © 2022 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
import AltStoreCore
|
||||
|
||||
struct AppPermissionsGrid: View {
|
||||
|
||||
let permissions: [AppPermission]
|
||||
|
||||
let columns = Array(repeating: GridItem(.flexible()), count: 3)
|
||||
|
||||
var body: some View {
|
||||
LazyVGrid(columns: columns) {
|
||||
ForEach(permissions, id: \.type) { permission in
|
||||
AppPermissionGridItemView(permission: permission)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AppPermissionGridItemView: View {
|
||||
let permission: AppPermission
|
||||
|
||||
@State var isPopoverPresented = false
|
||||
|
||||
var body: some View {
|
||||
SwiftUI.Button {
|
||||
self.isPopoverPresented = true
|
||||
} label: {
|
||||
VStack {
|
||||
Image(uiImage: permission.type.icon?.withRenderingMode(.alwaysTemplate) ?? UIImage(systemSymbol: .questionmark))
|
||||
.foregroundColor(.primary)
|
||||
.padding()
|
||||
.background(Circle().foregroundColor(Color(.secondarySystemBackground)))
|
||||
Text(permission.type.localizedShortName ?? permission.type.localizedName ?? "")
|
||||
}
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
.alert(isPresented: self.$isPopoverPresented) {
|
||||
Alert(title: Text(L10n.AppPermissionGrid.usageDescription), message: Text(permission.usageDescription))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//struct AppPermissionsGrid_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// AppPermissionsGrid()
|
||||
// }
|
||||
//}
|
||||
71
AltStore/Views/App Detail/AppScreenshotsPreview.swift
Normal file
71
AltStore/Views/App Detail/AppScreenshotsPreview.swift
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// AppScreenshotsPreview.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 23.12.22.
|
||||
// Copyright © 2022 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AsyncImage
|
||||
import AltStoreCore
|
||||
|
||||
struct AppScreenshotsPreview: View {
|
||||
|
||||
@Environment(\.dismiss)
|
||||
private var dismiss
|
||||
|
||||
let urls: [URL]
|
||||
let aspectRatio: CGFloat
|
||||
@State var index: Int
|
||||
|
||||
init(urls: [URL], aspectRatio: CGFloat = 9/16, initialIndex: Int = 0) {
|
||||
self.urls = urls
|
||||
self.aspectRatio = aspectRatio
|
||||
self._index = State(initialValue: initialIndex)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $index) {
|
||||
ForEach(Array(urls.enumerated()), id: \.offset) { (i, url) in
|
||||
AppScreenshot(url: url, aspectRatio: aspectRatio)
|
||||
.padding()
|
||||
.tag(i)
|
||||
}
|
||||
}
|
||||
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
|
||||
.navigationTitle("\(index + 1) of \(self.urls.count)")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
SwiftUI.Button {
|
||||
self.dismiss()
|
||||
} label: {
|
||||
Text(L10n.Action.close)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AppScreenshotsPreview: Equatable {
|
||||
/// Prevent re-rendering of the view if the parameters didn't change
|
||||
static func == (lhs: AppScreenshotsPreview, rhs: AppScreenshotsPreview) -> Bool {
|
||||
lhs.urls == rhs.urls
|
||||
}
|
||||
}
|
||||
|
||||
struct AppScreenshotsPreview_Previews: PreviewProvider {
|
||||
|
||||
static let context = DatabaseManager.shared.viewContext
|
||||
static let app = StoreApp.makeAltStoreApp(in: context)
|
||||
|
||||
static var previews: some View {
|
||||
Color.clear
|
||||
.sheet(isPresented: .constant(true)) {
|
||||
NavigationView {
|
||||
AppScreenshotsPreview(urls: app.screenshotURLs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
71
AltStore/Views/App Detail/AppScreenshotsScrollView.swift
Normal file
71
AltStore/Views/App Detail/AppScreenshotsScrollView.swift
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// AppScreenshotsScrollView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 27.11.22.
|
||||
// Copyright © 2022 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AsyncImage
|
||||
|
||||
|
||||
/// Horizontal ScrollView with an asynchronously loaded image for each screenshot URL
|
||||
///
|
||||
/// The struct inherits the `Equatable` protocol and implements the respective comparisation function to prevent the view from being constantly re-rendered when a `@State` change in the parent view occurs.
|
||||
/// This way, the `AppScreenshotsScrollView` will only be reloaded when the parameters change.
|
||||
struct AppScreenshotsScrollView: View {
|
||||
let urls: [URL]
|
||||
var aspectRatio: CGFloat = 9/16
|
||||
var height: CGFloat = 400
|
||||
|
||||
@State var selectedScreenshotIndex: Int?
|
||||
|
||||
var body: some View {
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack {
|
||||
ForEach(Array(urls.enumerated()), id: \.offset) { i, url in
|
||||
SwiftUI.Button {
|
||||
self.selectedScreenshotIndex = i
|
||||
} label: {
|
||||
AppScreenshot(url: url)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.frame(height: height)
|
||||
.shadow(radius: 12)
|
||||
.sheet(item: self.$selectedScreenshotIndex) { index in
|
||||
NavigationView {
|
||||
AppScreenshotsPreview(urls: urls, aspectRatio: aspectRatio, initialIndex: index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AppScreenshotsScrollView: Equatable {
|
||||
/// Prevent re-rendering of the view if the parameters didn't change
|
||||
static func == (lhs: AppScreenshotsScrollView, rhs: AppScreenshotsScrollView) -> Bool {
|
||||
lhs.urls == rhs.urls && lhs.aspectRatio == rhs.aspectRatio && lhs.height == rhs.height
|
||||
}
|
||||
}
|
||||
|
||||
extension Int: Identifiable {
|
||||
public var id: Int {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
import AltStoreCore
|
||||
|
||||
struct AppScreenshotsScrollView_Previews: PreviewProvider {
|
||||
|
||||
static let context = DatabaseManager.shared.viewContext
|
||||
static let app = StoreApp.makeAltStoreApp(in: context)
|
||||
|
||||
static var previews: some View {
|
||||
AppScreenshotsScrollView(urls: app.screenshotURLs)
|
||||
}
|
||||
}
|
||||
55
AltStore/Views/App Detail/AppVersionHistoryView.swift
Normal file
55
AltStore/Views/App Detail/AppVersionHistoryView.swift
Normal file
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// AppVersionHistoryView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 28.01.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AltStoreCore
|
||||
import ExpandableText
|
||||
|
||||
struct AppVersionHistoryView: View {
|
||||
let storeApp: StoreApp
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(storeApp.versions.sorted(by: { $0.date < $1.date }), id: \.version) { version in
|
||||
VStack(spacing: 8) {
|
||||
HStack {
|
||||
Text(version.version).bold()
|
||||
Spacer()
|
||||
Text(DateFormatterHelper.string(forRelativeDate: version.date))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
if let versionDescription = version.localizedDescription {
|
||||
ExpandableText(text: versionDescription)
|
||||
.lineLimit(3)
|
||||
.expandButton(TextSet(text: L10n.AppDetailView.more, font: .callout, color: .accentColor))
|
||||
.buttonStyle(.plain)
|
||||
} else {
|
||||
Text("No version desciption available")
|
||||
.italic()
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
.navigationTitle("Version History")
|
||||
}
|
||||
}
|
||||
|
||||
struct AppVersionHistoryView_Previews: PreviewProvider {
|
||||
|
||||
static let context = DatabaseManager.shared.viewContext
|
||||
static let app = StoreApp.makeAltStoreApp(in: context)
|
||||
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
AppVersionHistoryView(storeApp: app)
|
||||
}
|
||||
}
|
||||
}
|
||||
102
AltStore/Views/App Detail/WriteAppReviewView.swift
Normal file
102
AltStore/Views/App Detail/WriteAppReviewView.swift
Normal file
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// WriteAppReviewView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 19.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AltStoreCore
|
||||
|
||||
struct WriteAppReviewView: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
let storeApp: StoreApp
|
||||
|
||||
@State var currentRating = 0
|
||||
@State var reviewText = ""
|
||||
|
||||
var canSendReview: Bool {
|
||||
// Only allow the user to send the review if a rating has been set and
|
||||
// the review text is either empty or doesn't contain only whitespaces.
|
||||
self.currentRating > 0 && (
|
||||
self.reviewText.isEmpty || !self.reviewText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
// App Information
|
||||
HStack {
|
||||
AppIconView(iconUrl: storeApp.iconURL, size: 50)
|
||||
VStack(alignment: .leading) {
|
||||
Text(storeApp.name)
|
||||
.bold()
|
||||
Text(storeApp.developerName)
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
// Rating
|
||||
Section {
|
||||
HStack {
|
||||
Spacer()
|
||||
ForEach(1...5) { rating in
|
||||
SwiftUI.Button {
|
||||
self.currentRating = rating
|
||||
} label: {
|
||||
Image(systemSymbol: rating > self.currentRating ? .star : .starFill)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.frame(maxHeight: 40)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.foregroundColor(.yellow)
|
||||
} header: {
|
||||
Text("Rate the App")
|
||||
}
|
||||
|
||||
// Review
|
||||
Section {
|
||||
TextEditor(text: self.$reviewText)
|
||||
.frame(minHeight: 100, maxHeight: 250)
|
||||
} header: {
|
||||
Text("Leave a Review (optional)")
|
||||
}
|
||||
}
|
||||
.navigationTitle("Write a Review")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
SwiftUI.Button("Cancel", action: self.dismiss)
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
SwiftUI.Button("Send", action: self.sendReview)
|
||||
.disabled(!self.canSendReview)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func sendReview() {
|
||||
NotificationManager.shared.showNotification(title: "Feature not Implemented")
|
||||
self.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
struct WriteAppReviewView_Previews: PreviewProvider {
|
||||
|
||||
static let context = DatabaseManager.shared.viewContext
|
||||
static let app = StoreApp.makeAltStoreApp(in: context)
|
||||
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
WriteAppReviewView(storeApp: app)
|
||||
}
|
||||
}
|
||||
}
|
||||
56
AltStore/Views/Browse/AddSourceView.swift
Normal file
56
AltStore/Views/Browse/AddSourceView.swift
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// AddSourceView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 20.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
struct AddSourceView: View {
|
||||
|
||||
@State var sourceUrlText: String = ""
|
||||
|
||||
var continueHandler: (_ urlText: String) -> ()
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
TextField("https://connect.altstore.ml", text: $sourceUrlText)
|
||||
.keyboardType(.URL)
|
||||
.autocapitalization(.none)
|
||||
.autocorrectionDisabled()
|
||||
} header: {
|
||||
Text(L10n.AddSourceView.sourceURL)
|
||||
} footer: {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(L10n.AddSourceView.sourceWarning)
|
||||
|
||||
HStack(alignment: .top) {
|
||||
Image(systemSymbol: .exclamationmarkTriangleFill)
|
||||
|
||||
Text(L10n.AddSourceView.sourceWarningContinued)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwiftUI.Button {
|
||||
self.continueHandler(self.sourceUrlText)
|
||||
} label: {
|
||||
Text(L10n.AddSourceView.continue)
|
||||
}
|
||||
.disabled(URL(string: self.sourceUrlText)?.host == nil)
|
||||
}
|
||||
.listStyle(InsetGroupedListStyle())
|
||||
.navigationTitle(L10n.AddSourceView.title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
|
||||
struct AddSourceView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AddSourceView(continueHandler: { _ in })
|
||||
}
|
||||
}
|
||||
42
AltStore/Views/Browse/BrowseAppPreviewView.swift
Normal file
42
AltStore/Views/Browse/BrowseAppPreviewView.swift
Normal file
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// BrowseAppPreviewView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 20.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AsyncImage
|
||||
import AltStoreCore
|
||||
|
||||
struct BrowseAppPreviewView: View {
|
||||
let storeApp: StoreApp
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
AppRowView(app: storeApp)
|
||||
|
||||
if let subtitle = storeApp.subtitle {
|
||||
Text(subtitle)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
if !storeApp.screenshotURLs.isEmpty {
|
||||
HStack {
|
||||
ForEach(storeApp.screenshotURLs.prefix(2)) { url in
|
||||
AppScreenshot(url: url)
|
||||
}
|
||||
}
|
||||
.frame(height: 300)
|
||||
.shadow(radius: 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//struct BrowseAppPreviewView_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// BrowseAppPreviewView()
|
||||
// }
|
||||
//}
|
||||
165
AltStore/Views/Browse/BrowseView.swift
Normal file
165
AltStore/Views/Browse/BrowseView.swift
Normal file
@@ -0,0 +1,165 @@
|
||||
//
|
||||
// BrowseView.swift
|
||||
// SideStoreUI
|
||||
//
|
||||
// Created by Fabian Thies on 18.11.22.
|
||||
// Copyright © 2022 Fabian Thies. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
import AltStoreCore
|
||||
|
||||
struct BrowseView: View {
|
||||
@SwiftUI.FetchRequest(sortDescriptors: [
|
||||
NSSortDescriptor(keyPath: \StoreApp.sourceIdentifier, ascending: true),
|
||||
NSSortDescriptor(keyPath: \StoreApp.sortIndex, ascending: true),
|
||||
NSSortDescriptor(keyPath: \StoreApp.name, ascending: true),
|
||||
NSSortDescriptor(keyPath: \StoreApp.bundleIdentifier, ascending: true)
|
||||
], predicate: NSPredicate(format: "%K != %@", #keyPath(StoreApp.bundleIdentifier), StoreApp.altstoreAppID))
|
||||
var apps: FetchedResults<StoreApp>
|
||||
|
||||
var filteredApps: [StoreApp] {
|
||||
apps.items(matching: self.searchText)
|
||||
}
|
||||
|
||||
@State
|
||||
var selectedStoreApp: StoreApp?
|
||||
|
||||
@State var searchText = ""
|
||||
|
||||
@State var isShowingSourcesView = false
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
if searchText.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 32) {
|
||||
promotedCategoriesView
|
||||
|
||||
Text(L10n.BrowseView.Section.AllApps.title)
|
||||
.font(.title2)
|
||||
.bold()
|
||||
}
|
||||
}
|
||||
|
||||
if searchText.isEmpty, filteredApps.count == 0 {
|
||||
HintView {
|
||||
Text(L10n.BrowseView.Hints.NoApps.title)
|
||||
.bold()
|
||||
|
||||
Text(L10n.BrowseView.Hints.NoApps.text)
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
SwiftUI.Button {
|
||||
self.isShowingSourcesView = true
|
||||
} label: {
|
||||
Label(L10n.BrowseView.Hints.NoApps.addSource, systemSymbol: .plus)
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
.padding(.top, 8)
|
||||
}
|
||||
} else {
|
||||
LazyVStack(spacing: 32) {
|
||||
ForEach(filteredApps, id: \.bundleIdentifier) { app in
|
||||
NavigationLink {
|
||||
AppDetailView(storeApp: app)
|
||||
} label: {
|
||||
BrowseAppPreviewView(storeApp: app)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.searchable(text: self.$searchText, placeholder: L10n.BrowseView.search)
|
||||
}
|
||||
.background(Color(UIColor.systemGroupedBackground).ignoresSafeArea())
|
||||
.navigationTitle(L10n.BrowseView.title)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
SwiftUI.Button {
|
||||
self.isShowingSourcesView = true
|
||||
} label: {
|
||||
Text(L10n.BrowseView.Actions.sources)
|
||||
}
|
||||
.sheet(isPresented: self.$isShowingSourcesView) {
|
||||
NavigationView {
|
||||
SourcesView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
SwiftUI.Button {
|
||||
|
||||
} label: {
|
||||
Image(systemSymbol: .lineHorizontal3DecreaseCircle)
|
||||
.imageScale(.large)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var promotedCategoriesView: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
Text(L10n.BrowseView.Section.PromotedCategories.title)
|
||||
.font(.title2)
|
||||
.bold()
|
||||
Spacer()
|
||||
SwiftUI.Button(action: {}, label: {
|
||||
Text(L10n.BrowseView.Section.PromotedCategories.showAll)
|
||||
})
|
||||
.font(.callout)
|
||||
}
|
||||
|
||||
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())]) {
|
||||
PromotedCategoryView()
|
||||
.shadow(color: .black.opacity(0.1), radius: 8, y: 5)
|
||||
|
||||
PromotedCategoryView()
|
||||
.shadow(color: .black.opacity(0.1), radius: 8, y: 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BrowseView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
BrowseView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct PromotedCategoryView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
GeometryReader { proxy in
|
||||
RadialGradient(colors: [
|
||||
Color(UIColor(hexString: "477E84")!),
|
||||
Color(UIColor.secondarySystemBackground),
|
||||
Color(UIColor.secondarySystemBackground),
|
||||
Color(UIColor(hexString: "C38FF5")!)
|
||||
], center: .bottomLeading, startRadius: 0, endRadius: proxy.size.width)
|
||||
}
|
||||
|
||||
HStack {
|
||||
Image(systemSymbol: .dpadRightFill)
|
||||
Text(L10n.BrowseView.Categories.gamesAndEmulators)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
.foregroundColor(.accentColor)
|
||||
.padding()
|
||||
}
|
||||
.aspectRatio(21/9, contentMode: .fill)
|
||||
.frame(maxWidth: .infinity)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user