mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-08 22:33:26 +01:00
Add onboarding screens for an easy setup of SideStore
This commit is contained in:
@@ -63,6 +63,10 @@
|
||||
1F943C6F2927F90400ABE095 /* NewsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5B82927E0EE00B8D837 /* NewsView.swift */; };
|
||||
1F943C702927F90400ABE095 /* BrowseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5BA2927E0F800B8D837 /* BrowseView.swift */; };
|
||||
1F943C712927F90400ABE095 /* MyAppsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5BD2927E10D00B8D837 /* MyAppsView.swift */; };
|
||||
1F981B1129AA0FAE0014950E /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F981B1029AA0FAE0014950E /* OnboardingView.swift */; };
|
||||
1F981B1329AA101F0014950E /* OnboardingStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F981B1229AA101F0014950E /* OnboardingStepView.swift */; };
|
||||
1F981B1529AA1E070014950E /* AppIconsShowcase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F981B1429AA1E070014950E /* AppIconsShowcase.swift */; };
|
||||
1F981B1729AA34A70014950E /* AppStoreProductView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F981B1629AA34A70014950E /* AppStoreProductView.swift */; };
|
||||
1FA1C8CA294906890083119D /* MyAppsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA1C8C9294906890083119D /* MyAppsViewModel.swift */; };
|
||||
1FA5A6CA298E8B2F007BA946 /* RefreshAttemptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA5A6C9298E8B2F007BA946 /* RefreshAttemptsView.swift */; };
|
||||
1FA5A6CC298E8FE4007BA946 /* MailComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA5A6CB298E8FE4007BA946 /* MailComposeView.swift */; };
|
||||
@@ -76,6 +80,8 @@
|
||||
1FB96FCF292BBBCA007E68D1 /* SiriShortcutSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FCE292BBBC9007E68D1 /* SiriShortcutSetupView.swift */; };
|
||||
1FB96FEC292C171D007E68D1 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FEB292C171D007E68D1 /* NotificationManager.swift */; };
|
||||
1FB96FF3292D0539007E68D1 /* PillButtonProgressViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FF2292D0539007E68D1 /* PillButtonProgressViewStyle.swift */; };
|
||||
1FF8C6182A1780C60041352C /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF8C6172A1780C60041352C /* ActivityView.swift */; };
|
||||
1FF8C61B2A1782F10041352C /* Reachability in Frameworks */ = {isa = PBXBuildFile; productRef = 1FF8C61A2A1782F10041352C /* Reachability */; };
|
||||
1FFA56C2299994390011B6F5 /* OutputCapturer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FFA56C1299994390011B6F5 /* OutputCapturer.swift */; };
|
||||
1FFA56C52999978C0011B6F5 /* LocalConsole in Frameworks */ = {isa = PBXBuildFile; productRef = 1FFA56C42999978C0011B6F5 /* LocalConsole */; };
|
||||
1FFEF104298552DB0098374C /* AppVersionHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FFEF103298552DB0098374C /* AppVersionHistoryView.swift */; };
|
||||
@@ -609,6 +615,10 @@
|
||||
1F943C632927EF4200ABE095 /* NewsItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsItemView.swift; sourceTree = "<group>"; };
|
||||
1F943C652927F36600ABE095 /* NewsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsViewModel.swift; sourceTree = "<group>"; };
|
||||
1F943C672927F39400ABE095 /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = "<group>"; };
|
||||
1F981B1029AA0FAE0014950E /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
|
||||
1F981B1229AA101F0014950E /* OnboardingStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStepView.swift; sourceTree = "<group>"; };
|
||||
1F981B1429AA1E070014950E /* AppIconsShowcase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconsShowcase.swift; sourceTree = "<group>"; };
|
||||
1F981B1629AA34A70014950E /* AppStoreProductView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreProductView.swift; sourceTree = "<group>"; };
|
||||
1FA1C8C9294906890083119D /* MyAppsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppsViewModel.swift; sourceTree = "<group>"; };
|
||||
1FA5A6C9298E8B2F007BA946 /* RefreshAttemptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshAttemptsView.swift; sourceTree = "<group>"; };
|
||||
1FA5A6CB298E8FE4007BA946 /* MailComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailComposeView.swift; sourceTree = "<group>"; };
|
||||
@@ -629,6 +639,7 @@
|
||||
1FB96FCE292BBBC9007E68D1 /* SiriShortcutSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiriShortcutSetupView.swift; sourceTree = "<group>"; };
|
||||
1FB96FEB292C171D007E68D1 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
|
||||
1FB96FF2292D0539007E68D1 /* PillButtonProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButtonProgressViewStyle.swift; sourceTree = "<group>"; };
|
||||
1FF8C6172A1780C60041352C /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = "<group>"; };
|
||||
1FFA56C1299994390011B6F5 /* OutputCapturer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputCapturer.swift; sourceTree = "<group>"; };
|
||||
1FFEF103298552DB0098374C /* AppVersionHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionHistoryView.swift; sourceTree = "<group>"; };
|
||||
9961EC2D29BE9F2E00AF2C6F /* minimuxer-helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "minimuxer-helpers.swift"; path = "Dependencies/minimuxer/minimuxer-helpers.swift"; sourceTree = SOURCE_ROOT; };
|
||||
@@ -1046,6 +1057,7 @@
|
||||
D533E8B72727841800A9B5DD /* libAppleArchive.tbd in Frameworks */,
|
||||
B3C395F9284F362400DA9E2F /* AppCenterCrashes in Frameworks */,
|
||||
9922FFEC29B501C50020F868 /* Starscream in Frameworks */,
|
||||
1FF8C61B2A1782F10041352C /* Reachability in Frameworks */,
|
||||
D533E8BE2727BBF800A9B5DD /* libcurl.a in Frameworks */,
|
||||
4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */,
|
||||
B3C395F4284F35DD00DA9E2F /* Nuke in Frameworks */,
|
||||
@@ -1126,15 +1138,28 @@
|
||||
1F6284D6295218980060AAD8 /* DocumentPicker.swift */,
|
||||
1F545E84298D84CF00589F68 /* FilePreviewView.swift */,
|
||||
1FA5A6CB298E8FE4007BA946 /* MailComposeView.swift */,
|
||||
1F981B1629AA34A70014950E /* AppStoreProductView.swift */,
|
||||
1FF8C6172A1780C60041352C /* ActivityView.swift */,
|
||||
);
|
||||
path = "UIView Representables";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1F981B0F29AA0F9B0014950E /* Onboarding */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1F981B1029AA0FAE0014950E /* OnboardingView.swift */,
|
||||
1F981B1229AA101F0014950E /* OnboardingStepView.swift */,
|
||||
1F981B1429AA1E070014950E /* AppIconsShowcase.swift */,
|
||||
);
|
||||
path = Onboarding;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1FAFC5B02927E01400B8D837 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1FAFC5B52927E06300B8D837 /* RootView.swift */,
|
||||
1FAFC5B12927E02E00B8D837 /* Authentication */,
|
||||
1F981B0F29AA0F9B0014950E /* Onboarding */,
|
||||
1FAFC5B22927E03300B8D837 /* News */,
|
||||
1FAFC5B32927E03D00B8D837 /* Browse */,
|
||||
1FAFC5BC2927E0FD00B8D837 /* My Apps */,
|
||||
@@ -2312,6 +2337,7 @@
|
||||
1F07F5662955D16A00F7BE95 /* SFSafeSymbols */,
|
||||
1F1295802989B51F0048FCB9 /* ExpandableText */,
|
||||
1FFA56C42999978C0011B6F5 /* LocalConsole */,
|
||||
1FF8C61A2A1782F10041352C /* Reachability */,
|
||||
);
|
||||
productName = AltStore;
|
||||
productReference = BFD2476A2284B9A500981D42 /* SideStore.app */;
|
||||
@@ -2389,6 +2415,7 @@
|
||||
1F07F5652955D16A00F7BE95 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */,
|
||||
1F12957F2989B51F0048FCB9 /* XCRemoteSwiftPackageReference "ExpandableText" */,
|
||||
1FFA56C32999978C0011B6F5 /* XCRemoteSwiftPackageReference "LocalConsole" */,
|
||||
1FF8C6192A1782F10041352C /* XCRemoteSwiftPackageReference "Reachability" */,
|
||||
);
|
||||
productRefGroup = BFD2476B2284B9A500981D42 /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -2814,6 +2841,7 @@
|
||||
BFC57A652416C72400EB891E /* DeactivateAppOperation.swift in Sources */,
|
||||
1F943C712927F90400ABE095 /* MyAppsView.swift in Sources */,
|
||||
1FA1C8CA294906890083119D /* MyAppsViewModel.swift in Sources */,
|
||||
1F981B1529AA1E070014950E /* AppIconsShowcase.swift in Sources */,
|
||||
BF3BEFC124086A1E00DE7D55 /* RefreshAppOperation.swift in Sources */,
|
||||
1FFEF104298552DB0098374C /* AppVersionHistoryView.swift in Sources */,
|
||||
BFE60740231AFD2A002B0E8E /* InsetGroupTableViewCell.swift in Sources */,
|
||||
@@ -2841,10 +2869,13 @@
|
||||
1FB96FEC292C171D007E68D1 /* NotificationManager.swift in Sources */,
|
||||
BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */,
|
||||
1F07F56B2955F11500F7BE95 /* AppScreenshotsPreview.swift in Sources */,
|
||||
1F981B1329AA101F0014950E /* OnboardingStepView.swift in Sources */,
|
||||
BF44EEFC246B4550002A52F2 /* RemoveAppOperation.swift in Sources */,
|
||||
BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */,
|
||||
1F981B1729AA34A70014950E /* AppStoreProductView.swift in Sources */,
|
||||
BFC57A6E2416FC5D00EB891E /* InstalledAppsCollectionHeaderView.swift in Sources */,
|
||||
B3EE16B62925E27D00B3B1F5 /* AnisetteManager.swift in Sources */,
|
||||
1FF8C6182A1780C60041352C /* ActivityView.swift in Sources */,
|
||||
1F943C6E2927F90400ABE095 /* NewsItemView.swift in Sources */,
|
||||
BF88F97224F8727D00BB75DF /* AppManagerErrors.swift in Sources */,
|
||||
B39F16152918D7DA002E9404 /* Consts+Proxy.swift in Sources */,
|
||||
@@ -2917,6 +2948,7 @@
|
||||
BF6C8FB02429599900125131 /* TextCollectionReusableView.swift in Sources */,
|
||||
BF663C4F2433ED8200DAA738 /* FileManager+DirectorySize.swift in Sources */,
|
||||
1F943C702927F90400ABE095 /* BrowseView.swift in Sources */,
|
||||
1F981B1129AA0FAE0014950E /* OnboardingView.swift in Sources */,
|
||||
1F943C692927F8F200ABE095 /* RootView.swift in Sources */,
|
||||
1FB84BA62928DE08006A5CF4 /* AppDetailView.swift in Sources */,
|
||||
D57DF63F271E51E400677701 /* ALTAppPatcher.m in Sources */,
|
||||
@@ -3772,6 +3804,14 @@
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
1FF8C6192A1782F10041352C /* XCRemoteSwiftPackageReference "Reachability" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/ashleymills/Reachability.swift";
|
||||
requirement = {
|
||||
branch = master;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
1FFA56C32999978C0011B6F5 /* XCRemoteSwiftPackageReference "LocalConsole" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/duraidabdul/LocalConsole.git";
|
||||
@@ -3893,6 +3933,11 @@
|
||||
package = 1F74FF1C295263510047C051 /* XCRemoteSwiftPackageReference "AsyncImage" */;
|
||||
productName = AsyncImage;
|
||||
};
|
||||
1FF8C61A2A1782F10041352C /* Reachability */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 1FF8C6192A1782F10041352C /* XCRemoteSwiftPackageReference "Reachability" */;
|
||||
productName = Reachability;
|
||||
};
|
||||
1FFA56C42999978C0011B6F5 /* LocalConsole */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 1FFA56C32999978C0011B6F5 /* XCRemoteSwiftPackageReference "LocalConsole" */;
|
||||
|
||||
@@ -90,6 +90,15 @@
|
||||
"version" : "1.10.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "reachability.swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/ashleymills/Reachability.swift",
|
||||
"state" : {
|
||||
"branch" : "master",
|
||||
"revision" : "a81b7367f2c46875f29577e03a60c39cdfad0c8d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "semanticversion",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
||||
@@ -54,15 +54,29 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
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(step: .pairing)
|
||||
return
|
||||
}
|
||||
start_minimuxer_threads(pf)
|
||||
#endif
|
||||
}
|
||||
|
||||
func showOnboarding(step: OnboardingView.OnboardingStep = .welcome) {
|
||||
let onboardingView = OnboardingView(onDismiss: { self.dismiss(animated: true) }, currentStep: step)
|
||||
.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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
89
AltStore/Views/Onboarding/AppIconsShowcase.swift
Normal file
89
AltStore/Views/Onboarding/AppIconsShowcase.swift
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// AppIconsShowcase.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 25.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AppIconsShowcase: View {
|
||||
|
||||
@State var animationProgress = 0.0
|
||||
@State var animation2Progress = 0.0
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
GeometryReader { proxy in
|
||||
ZStack(alignment: .bottom) {
|
||||
Image(uiImage: UIImage(named: "AppIcon")!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 0.2 * proxy.size.width)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24, style: .circular))
|
||||
.offset(x: -0.3*proxy.size.width * self.animationProgress, y: -30)
|
||||
.rotationEffect(.degrees(-20 * self.animationProgress))
|
||||
.shadow(radius: 8 * self.animationProgress)
|
||||
|
||||
Image(uiImage: UIImage(named: "AppIcon")!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 0.25 * proxy.size.width)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24, style: .circular))
|
||||
.offset(x: -0.15*proxy.size.width * self.animationProgress, y: -10)
|
||||
.rotationEffect(.degrees(-10 * self.animationProgress))
|
||||
.shadow(radius: 12 * self.animationProgress)
|
||||
|
||||
Image(uiImage: UIImage(named: "AppIcon")!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 0.2 * proxy.size.width)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24, style: .circular))
|
||||
.offset(x: self.animationProgress*0.3*proxy.size.width, y: -30)
|
||||
.rotationEffect(.degrees(self.animationProgress*20))
|
||||
.shadow(radius: 8 * self.animationProgress)
|
||||
|
||||
Image(uiImage: UIImage(named: "AppIcon")!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 0.25 * proxy.size.width)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24, style: .circular))
|
||||
.offset(x: self.animationProgress * 0.15*proxy.size.width, y: -10)
|
||||
.rotationEffect(.degrees(self.animationProgress * 10))
|
||||
.shadow(radius: 12 * self.animationProgress)
|
||||
|
||||
Image(uiImage: UIImage(named: "AppIcon")!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 0.3 * proxy.size.width)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24, style: .circular))
|
||||
.shadow(radius: 16 * self.animationProgress + 8 * self.animation2Progress)
|
||||
.scaleEffect(1.0 + 0.05 * self.animation2Progress)
|
||||
}
|
||||
.frame(maxWidth: proxy.size.width)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
withAnimation(.spring()) {
|
||||
self.animationProgress = 1.0
|
||||
self.animation2Progress = 1.0
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
|
||||
withAnimation(.spring()) {
|
||||
self.animation2Progress = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AppIconsShowcase_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AppIconsShowcase()
|
||||
.frame(height: 150)
|
||||
}
|
||||
}
|
||||
137
AltStore/Views/Onboarding/OnboardingStepView.swift
Normal file
137
AltStore/Views/Onboarding/OnboardingStepView.swift
Normal file
@@ -0,0 +1,137 @@
|
||||
//
|
||||
// OnboardingStepView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 25.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
|
||||
struct OnboardingStep<Title: View, Hero: View, Content: View, Action: View> {
|
||||
|
||||
@ViewBuilder
|
||||
var title: Title
|
||||
|
||||
@ViewBuilder
|
||||
var hero: Hero
|
||||
|
||||
@ViewBuilder
|
||||
var content: Content
|
||||
|
||||
@ViewBuilder
|
||||
var action: Action
|
||||
}
|
||||
|
||||
|
||||
struct OnboardingStepView<Title: View, Hero: View, Content: View, Action: View>: View {
|
||||
|
||||
@ViewBuilder
|
||||
var title: Title
|
||||
|
||||
@ViewBuilder
|
||||
var hero: Hero
|
||||
|
||||
@ViewBuilder
|
||||
var content: Content
|
||||
|
||||
@ViewBuilder
|
||||
var action: Action
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 64) {
|
||||
self.title
|
||||
.font(.largeTitle.weight(.bold))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
self.hero
|
||||
.frame(height: 150)
|
||||
|
||||
self.content
|
||||
|
||||
Spacer()
|
||||
|
||||
self.action
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct OnboardingStepView_Previews: PreviewProvider {
|
||||
@State
|
||||
static var isWireGuardAppStorePageVisible = false
|
||||
|
||||
static var previews: some View {
|
||||
OnboardingStepView(title: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Welcome to")
|
||||
Text("SideStore")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}, hero: {
|
||||
AppIconsShowcase()
|
||||
}, content: {
|
||||
VStack(spacing: 16) {
|
||||
Text("Before you can start sideloading apps, there is some setup to do.")
|
||||
Text("The following setup will guide you through the steps one by one.")
|
||||
Text("You will need a computer (Windows, macOS, Linux) and your Apple ID.")
|
||||
}
|
||||
}, action: {
|
||||
SwiftUI.Button("Continue") {
|
||||
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
})
|
||||
|
||||
OnboardingStepView(title: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Pair your Device")
|
||||
}
|
||||
}, hero: {
|
||||
Image(systemSymbol: .link)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.accentColor)
|
||||
.shadow(color: .accentColor.opacity(0.8), radius: 12)
|
||||
}, content: {
|
||||
VStack {
|
||||
Text("Before you can start sideloading apps, there is some setup to do.")
|
||||
Text("The following setup will guide you through the steps one by one.")
|
||||
}
|
||||
}, action: {
|
||||
SwiftUI.Button("Continue") {
|
||||
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
})
|
||||
|
||||
OnboardingStepView(title: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Download WireGuard")
|
||||
}
|
||||
}, hero: {
|
||||
Image(systemSymbol: .icloudAndArrowDown)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.accentColor)
|
||||
.shadow(color: .accentColor.opacity(0.8), radius: 12)
|
||||
}, content: {
|
||||
VStack {
|
||||
Text("Before you can start sideloading apps, there is some setup to do.")
|
||||
Text("The following setup will guide you through the steps one by one.")
|
||||
}
|
||||
}, action: {
|
||||
SwiftUI.Button("Show in App Store") {
|
||||
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
|
||||
AppStoreView(isVisible: self.$isWireGuardAppStorePageVisible, itunesItemId: 1441195209)
|
||||
.frame(width: .zero, height: .zero)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
444
AltStore/Views/Onboarding/OnboardingView.swift
Normal file
444
AltStore/Views/Onboarding/OnboardingView.swift
Normal file
@@ -0,0 +1,444 @@
|
||||
//
|
||||
// OnboardingView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 25.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
import AltStoreCore
|
||||
import minimuxer
|
||||
import Reachability
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
|
||||
struct OnboardingView: View {
|
||||
enum OnboardingStep: Int {
|
||||
case welcome, pairing, wireguard, wireguardConfig, addSources, finish
|
||||
}
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
// Temporary workaround for UIKit compatibility
|
||||
var onDismiss: (() -> Void)? = nil
|
||||
|
||||
@State var currentStep: OnboardingStep = .wireguard //.welcome
|
||||
@State private var pairingFileURL: URL? = nil
|
||||
@State private var isWireGuardAppStorePageVisible: Bool = false
|
||||
@State private var isDownloadingWireGuardProfile: Bool = false
|
||||
@State private var wireGuardProfileFileURL: URL? = nil
|
||||
@State private var reachabilityNotifier: Reachability? = nil
|
||||
@State private var isWireGuardTunnelReachable: Bool = false
|
||||
@State private var areTrustedSourcesEnabled: Bool = false
|
||||
@State private var isLoadingTrustedSources: Bool = false
|
||||
|
||||
let pairingFileTypes = UTType.types(tag: "plist", tagClass: UTTagClass.filenameExtension, conformingTo: nil) + UTType.types(tag: "mobiledevicepairing", tagClass: UTTagClass.filenameExtension, conformingTo: UTType.data) + [.xml]
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: self.$currentStep) {
|
||||
welcomeStep
|
||||
.tag(OnboardingStep.welcome)
|
||||
.highPriorityGesture(DragGesture())
|
||||
|
||||
pairingView
|
||||
.tag(OnboardingStep.pairing)
|
||||
.highPriorityGesture(DragGesture())
|
||||
|
||||
wireguardView
|
||||
.tag(OnboardingStep.wireguard)
|
||||
.highPriorityGesture(DragGesture())
|
||||
|
||||
wireguardConfigView
|
||||
.tag(OnboardingStep.wireguardConfig)
|
||||
.highPriorityGesture(DragGesture())
|
||||
|
||||
addSourcesView
|
||||
.tag(OnboardingStep.addSources)
|
||||
.highPriorityGesture(DragGesture())
|
||||
|
||||
finishView
|
||||
.tag(OnboardingStep.finish)
|
||||
.highPriorityGesture(DragGesture())
|
||||
|
||||
}
|
||||
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
.background(Color.accentColor.opacity(0.1).edgesIgnoringSafeArea(.all))
|
||||
.onChange(of: self.currentStep) { step in
|
||||
switch step {
|
||||
case .wireguardConfig:
|
||||
self.startPingingWireGuardTunnel()
|
||||
default:
|
||||
self.stopPingingWireGuardTunnel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showNextStep() {
|
||||
withAnimation {
|
||||
self.currentStep = OnboardingStep(rawValue: self.currentStep.rawValue + 1) ?? self.currentStep
|
||||
}
|
||||
}
|
||||
|
||||
var welcomeStep: some View {
|
||||
OnboardingStepView {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Welcome to")
|
||||
Text("SideStore")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
} hero: {
|
||||
AppIconsShowcase()
|
||||
} content: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Before you can start sideloading apps, there is some setup to do.")
|
||||
Text("The following setup will guide you through the steps one by one.")
|
||||
Text("You will need a computer (Windows, macOS, Linux) and your Apple ID.")
|
||||
}
|
||||
} action: {
|
||||
SwiftUI.Button("Continue") {
|
||||
self.showNextStep()
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
}
|
||||
}
|
||||
|
||||
var pairingView: some View {
|
||||
OnboardingStepView(title: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Pair your Device")
|
||||
}
|
||||
}, hero: {
|
||||
Image(systemSymbol: .link)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.accentColor)
|
||||
.shadow(color: .accentColor.opacity(0.8), radius: 12)
|
||||
}, content: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("SideStore supports sideloading even on non-jailbroken devices.")
|
||||
Text("For it to work, you have to generate a pairing file as described [here in our documentation](https://wiki.sidestore.io/guides/install#pairing-process).")
|
||||
Text("Once you have the `<UUID>.mobiledevicepairing`, import it using the button below.")
|
||||
}
|
||||
}, action: {
|
||||
ModalNavigationLink("Select Pairing File") {
|
||||
DocumentPicker(selectedUrl: self.$pairingFileURL,
|
||||
supportedTypes: self.pairingFileTypes.map { $0.identifier })
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
.onChange(of: self.pairingFileURL) { newValue in
|
||||
guard let url = newValue else {
|
||||
return
|
||||
}
|
||||
|
||||
self.importPairingFile(url: url)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var wireguardView: some View {
|
||||
OnboardingStepView(title: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Download WireGuard")
|
||||
}
|
||||
}, hero: {
|
||||
Image(systemSymbol: .icloudAndArrowDown)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.accentColor)
|
||||
.shadow(color: .accentColor.opacity(0.8), radius: 12)
|
||||
}, content: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("To sideload and sign app on-device without the need of a computer program like SideServer, a local WireGuard connection is required.")
|
||||
Text("This connection is strictly local-only and does not connect to a server on the internet.")
|
||||
Text("First, download WireGuard from the App Store (free).")
|
||||
}
|
||||
}, action: {
|
||||
AppStoreView(isVisible: self.$isWireGuardAppStorePageVisible, itunesItemId: 1441195209)
|
||||
.frame(width: .zero, height: .zero)
|
||||
|
||||
VStack {
|
||||
SwiftUI.Button("Show in App Store") {
|
||||
self.isWireGuardAppStorePageVisible = true
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
|
||||
SwiftUI.Button("Continue") {
|
||||
self.showNextStep()
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
var wireguardConfigView: some View {
|
||||
OnboardingStepView(title: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Enable the WireGuard Tunnel")
|
||||
}
|
||||
}, hero: {
|
||||
Image(systemSymbol: .network)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.accentColor)
|
||||
.shadow(color: .accentColor.opacity(0.8), radius: 12)
|
||||
}, content: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Once WireGuard is installed, a configuration file has to be installed in the WireGuard app.")
|
||||
Text("Tap the button below and open the downloaded file in the WireGuard app.")
|
||||
Text("Then, activate the VPN tunnel to continue.")
|
||||
}
|
||||
}, action: {
|
||||
VStack {
|
||||
SwiftUI.Button("Download and Install Configuration File") {
|
||||
self.downloadWireGuardProfile()
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle(isLoading: self.isDownloadingWireGuardProfile))
|
||||
.sheet(item: self.$wireGuardProfileFileURL) { fileURL in
|
||||
ActivityView(items: [fileURL])
|
||||
}
|
||||
|
||||
SwiftUI.Button(self.isWireGuardTunnelReachable ? "Continue" : "Waiting for connection...",
|
||||
action: self.showNextStep)
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
.disabled(!self.isWireGuardTunnelReachable)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var addSourcesView: some View {
|
||||
OnboardingStepView(title: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Add Sources")
|
||||
}
|
||||
}, hero: {
|
||||
Image(systemSymbol: .booksVertical)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.accentColor)
|
||||
.shadow(color: .accentColor.opacity(0.8), radius: 12)
|
||||
}, content: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("All apps are provided through sources, which anyone can create and share with the world.")
|
||||
Text("We have compiled a list of trusted sources for SideStore which you can enable to start sideloading your favorite apps.")
|
||||
Text("By default, only the source containing SideStore itself is enabled.")
|
||||
|
||||
Toggle("Enable Trusted Sources", isOn: $areTrustedSourcesEnabled)
|
||||
}
|
||||
}, action: {
|
||||
SwiftUI.Button("Continue") {
|
||||
self.setupTrustedSources()
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle(isLoading: self.isLoadingTrustedSources))
|
||||
.disabled(self.isLoadingTrustedSources)
|
||||
})
|
||||
}
|
||||
|
||||
var finishView: some View {
|
||||
OnboardingStepView(title: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Setup Completed")
|
||||
}
|
||||
}, hero: {
|
||||
Image(systemSymbol: .checkmark)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.accentColor)
|
||||
.shadow(color: .accentColor.opacity(0.8), radius: 12)
|
||||
}, content: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Congratulations, you did it! 🎉")
|
||||
Text("You can start your sideloading journey.")
|
||||
}
|
||||
}, action: {
|
||||
SwiftUI.Button("Let's Go") {
|
||||
self.finishOnboarding()
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extension OnboardingView {
|
||||
func importPairingFile(url: URL) {
|
||||
let isSecuredURL = url.startAccessingSecurityScopedResource() == true
|
||||
|
||||
do {
|
||||
// Read to a string
|
||||
let data = try Data(contentsOf: url)
|
||||
let pairing_string = String(bytes: data, encoding: .utf8)
|
||||
if pairing_string == nil {
|
||||
// TODO: Show error message
|
||||
debugPrint("Unable to read pairing file")
|
||||
// displayError("Unable to read pairing file")
|
||||
}
|
||||
|
||||
// Save to a file for next launch
|
||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||
let fm = FileManager.default
|
||||
let documentsPath = fm.documentsDirectory.appendingPathComponent("/\(filename)")
|
||||
try pairing_string?.write(to: documentsPath, atomically: true, encoding: String.Encoding.utf8)
|
||||
|
||||
// Start minimuxer now that we have a file
|
||||
start_minimuxer_threads(pairing_string!)
|
||||
|
||||
// Show the next onboarding step
|
||||
self.showNextStep()
|
||||
|
||||
} catch {
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
}
|
||||
|
||||
if (isSecuredURL) {
|
||||
url.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
}
|
||||
|
||||
func start_minimuxer_threads(_ pairing_file: String) {
|
||||
set_usbmuxd_socket()
|
||||
#if false // Retries
|
||||
var res = start_minimuxer(pairing_file: pairing_file)
|
||||
var attempts = 10
|
||||
while (attempts != 0 && res != 0) {
|
||||
print("start_minimuxer `res` != 0, retry #\(attempts)")
|
||||
res = start_minimuxer(pairing_file: pairing_file)
|
||||
attempts -= 1
|
||||
}
|
||||
#else
|
||||
let res = start_minimuxer(pairing_file: pairing_file)
|
||||
#endif
|
||||
if res != 0 {
|
||||
// TODO: Show error message
|
||||
debugPrint("minimuxer failed to start. Incorrect arguments were passed.")
|
||||
// displayError("minimuxer failed to start. Incorrect arguments were passed.")
|
||||
}
|
||||
auto_mount_dev_image()
|
||||
}
|
||||
}
|
||||
|
||||
extension OnboardingView {
|
||||
func downloadWireGuardProfile() {
|
||||
let profileDownloadUrl = "https://github.com/SideStore/SideStore/releases/download/0.3.1/SideStore.conf"
|
||||
let destinationUrl = FileManager.default.temporaryDirectory.appendingPathComponent("SideStore.conf")
|
||||
|
||||
self.isDownloadingWireGuardProfile = true
|
||||
URLSession.shared.dataTask(with: URLRequest(url: URL(string: profileDownloadUrl)!)) { data, response, error in
|
||||
|
||||
defer { self.isDownloadingWireGuardProfile = false }
|
||||
|
||||
if let error {
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
return
|
||||
}
|
||||
|
||||
guard let response = response as? HTTPURLResponse, 200..<300 ~= response.statusCode, let data else {
|
||||
// TODO: Show error message
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try data.write(to: destinationUrl)
|
||||
self.wireGuardProfileFileURL = destinationUrl
|
||||
} catch {
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
return
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
func startPingingWireGuardTunnel() {
|
||||
do {
|
||||
self.reachabilityNotifier = try Reachability(hostname: "10.7.0.1")
|
||||
self.reachabilityNotifier?.whenReachable = { _ in
|
||||
self.isWireGuardTunnelReachable = true
|
||||
}
|
||||
self.reachabilityNotifier?.whenUnreachable = { _ in
|
||||
self.isWireGuardTunnelReachable = false
|
||||
}
|
||||
|
||||
try self.reachabilityNotifier?.startNotifier()
|
||||
} catch {
|
||||
// TODO: Show error message
|
||||
debugPrint(error)
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
func stopPingingWireGuardTunnel() {
|
||||
self.reachabilityNotifier?.stopNotifier()
|
||||
}
|
||||
}
|
||||
|
||||
extension OnboardingView {
|
||||
func setupTrustedSources() {
|
||||
self.isLoadingTrustedSources = true
|
||||
|
||||
AppManager.shared.fetchTrustedSources { result in
|
||||
|
||||
switch result {
|
||||
case .success(let trustedSources):
|
||||
// Cache trusted source IDs.
|
||||
UserDefaults.shared.trustedSourceIDs = trustedSources.map { $0.identifier }
|
||||
|
||||
// Don't show sources without a sourceURL.
|
||||
let featuredSourceURLs = trustedSources.compactMap { $0.sourceURL }
|
||||
|
||||
// This context is never saved, but keeps the managed sources alive.
|
||||
let context = DatabaseManager.shared.persistentContainer.newBackgroundSavingViewContext()
|
||||
|
||||
let dispatchGroup = DispatchGroup()
|
||||
for sourceURL in featuredSourceURLs {
|
||||
dispatchGroup.enter()
|
||||
|
||||
AppManager.shared.fetchSource(sourceURL: sourceURL, managedObjectContext: context) { result in
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
|
||||
dispatchGroup.notify(queue: .main) {
|
||||
self.isLoadingTrustedSources = false
|
||||
|
||||
// Save the fetched trusted sources
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
}
|
||||
|
||||
self.showNextStep()
|
||||
}
|
||||
case .failure(let error):
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
self.isLoadingTrustedSources = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension OnboardingView {
|
||||
func finishOnboarding() {
|
||||
// Set the onboarding complete flag
|
||||
UserDefaults.standard.onboardingComplete = true
|
||||
|
||||
if let onDismiss {
|
||||
onDismiss()
|
||||
} else {
|
||||
self.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct OnboardingView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Color.red
|
||||
.ignoresSafeArea()
|
||||
.sheet(isPresented: .constant(true)) {
|
||||
OnboardingView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ public extension UserDefaults
|
||||
}()
|
||||
|
||||
@NSManaged var firstLaunch: Date?
|
||||
@NSManaged var onboardingComplete: Bool
|
||||
@NSManaged var requiresAppGroupMigration: Bool
|
||||
@NSManaged var textServer: Bool
|
||||
@NSManaged var textInputAnisetteURL: String?
|
||||
@@ -71,6 +72,7 @@ public extension UserDefaults
|
||||
let localServerSupportsRefreshing = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14)
|
||||
|
||||
let defaults = [
|
||||
#keyPath(UserDefaults.onboardingComplete): false,
|
||||
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
||||
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
||||
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
|
||||
|
||||
Reference in New Issue
Block a user