mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
[ADD] Debug entries for refresh attempts, sending feedback, advanced settings, and resetting the pairing file
This commit is contained in:
@@ -63,6 +63,8 @@
|
|||||||
1F943C702927F90400ABE095 /* BrowseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5BA2927E0F800B8D837 /* BrowseView.swift */; };
|
1F943C702927F90400ABE095 /* BrowseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5BA2927E0F800B8D837 /* BrowseView.swift */; };
|
||||||
1F943C712927F90400ABE095 /* MyAppsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5BD2927E10D00B8D837 /* MyAppsView.swift */; };
|
1F943C712927F90400ABE095 /* MyAppsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5BD2927E10D00B8D837 /* MyAppsView.swift */; };
|
||||||
1FA1C8CA294906890083119D /* MyAppsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA1C8C9294906890083119D /* MyAppsViewModel.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 */; };
|
||||||
1FB84BA62928DE08006A5CF4 /* AppDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB84BA52928DE08006A5CF4 /* AppDetailView.swift */; };
|
1FB84BA62928DE08006A5CF4 /* AppDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB84BA52928DE08006A5CF4 /* AppDetailView.swift */; };
|
||||||
1FB96FBE292A20E5007E68D1 /* ObservableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FBD292A20E5007E68D1 /* ObservableScrollView.swift */; };
|
1FB96FBE292A20E5007E68D1 /* ObservableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FBD292A20E5007E68D1 /* ObservableScrollView.swift */; };
|
||||||
1FB96FC0292A63F2007E68D1 /* AppPillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FBF292A63F2007E68D1 /* AppPillButton.swift */; };
|
1FB96FC0292A63F2007E68D1 /* AppPillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FBF292A63F2007E68D1 /* AppPillButton.swift */; };
|
||||||
@@ -604,6 +606,8 @@
|
|||||||
1F943C652927F36600ABE095 /* NewsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsViewModel.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>"; };
|
1F943C672927F39400ABE095 /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = "<group>"; };
|
||||||
1FA1C8C9294906890083119D /* MyAppsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppsViewModel.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>"; };
|
||||||
1FAFC5A42927E00000B8D837 /* SideStoreUIApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideStoreUIApp.swift; sourceTree = "<group>"; };
|
1FAFC5A42927E00000B8D837 /* SideStoreUIApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideStoreUIApp.swift; sourceTree = "<group>"; };
|
||||||
1FAFC5B52927E06300B8D837 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
|
1FAFC5B52927E06300B8D837 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
|
||||||
1FAFC5B82927E0EE00B8D837 /* NewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsView.swift; sourceTree = "<group>"; };
|
1FAFC5B82927E0EE00B8D837 /* NewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsView.swift; sourceTree = "<group>"; };
|
||||||
@@ -1115,6 +1119,7 @@
|
|||||||
1F66F5B92938CA5700A910CA /* VisualEffectView.swift */,
|
1F66F5B92938CA5700A910CA /* VisualEffectView.swift */,
|
||||||
1F6284D6295218980060AAD8 /* DocumentPicker.swift */,
|
1F6284D6295218980060AAD8 /* DocumentPicker.swift */,
|
||||||
1F545E84298D84CF00589F68 /* FilePreviewView.swift */,
|
1F545E84298D84CF00589F68 /* FilePreviewView.swift */,
|
||||||
|
1FA5A6CB298E8FE4007BA946 /* MailComposeView.swift */,
|
||||||
);
|
);
|
||||||
path = "UIView Representables";
|
path = "UIView Representables";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1200,6 +1205,7 @@
|
|||||||
1F0DD83E29367F6C007608A4 /* ConnectAppleIDView.swift */,
|
1F0DD83E29367F6C007608A4 /* ConnectAppleIDView.swift */,
|
||||||
1F2EF786297C4D40002FD839 /* LicensesView.swift */,
|
1F2EF786297C4D40002FD839 /* LicensesView.swift */,
|
||||||
1F545E82298D79E400589F68 /* ErrorLogView.swift */,
|
1F545E82298D79E400589F68 /* ErrorLogView.swift */,
|
||||||
|
1FA5A6C9298E8B2F007BA946 /* RefreshAttemptsView.swift */,
|
||||||
);
|
);
|
||||||
path = Settings;
|
path = Settings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2876,6 +2882,7 @@
|
|||||||
BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */,
|
BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */,
|
||||||
BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */,
|
BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */,
|
||||||
1F66F5BC2938F03700A910CA /* Modifiers.swift in Sources */,
|
1F66F5BC2938F03700A910CA /* Modifiers.swift in Sources */,
|
||||||
|
1FA5A6CA298E8B2F007BA946 /* RefreshAttemptsView.swift in Sources */,
|
||||||
1F5DF9D82974426300DDAA47 /* AppScreenshot.swift in Sources */,
|
1F5DF9D82974426300DDAA47 /* AppScreenshot.swift in Sources */,
|
||||||
1F66F5BA2938CA5700A910CA /* VisualEffectView.swift in Sources */,
|
1F66F5BA2938CA5700A910CA /* VisualEffectView.swift in Sources */,
|
||||||
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
||||||
@@ -2902,6 +2909,7 @@
|
|||||||
1FB84BA62928DE08006A5CF4 /* AppDetailView.swift in Sources */,
|
1FB84BA62928DE08006A5CF4 /* AppDetailView.swift in Sources */,
|
||||||
D57DF63F271E51E400677701 /* ALTAppPatcher.m in Sources */,
|
D57DF63F271E51E400677701 /* ALTAppPatcher.m in Sources */,
|
||||||
BFB6B220231870B00022A802 /* NewsCollectionViewCell.swift in Sources */,
|
BFB6B220231870B00022A802 /* NewsCollectionViewCell.swift in Sources */,
|
||||||
|
1FA5A6CC298E8FE4007BA946 /* MailComposeView.swift in Sources */,
|
||||||
BF3BEFBF2408673400DE7D55 /* FetchProvisioningProfilesOperation.swift in Sources */,
|
BF3BEFBF2408673400DE7D55 /* FetchProvisioningProfilesOperation.swift in Sources */,
|
||||||
BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */,
|
BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */,
|
||||||
1FB96FC5292A7251007E68D1 /* BrowseAppPreviewView.swift in Sources */,
|
1FB96FC5292A7251007E68D1 /* BrowseAppPreviewView.swift in Sources */,
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class NotificationManager: ObservableObject {
|
|||||||
self.showNotification(title: text, detailText: detailText)
|
self.showNotification(title: text, detailText: detailText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func showNotification(title: String, detailText: String?) {
|
func showNotification(title: String, detailText: String? = nil) {
|
||||||
let notificationId = UUID()
|
let notificationId = UUID()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
90
AltStore/Views/Settings/RefreshAttemptsView.swift
Normal file
90
AltStore/Views/Settings/RefreshAttemptsView.swift
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
//
|
||||||
|
// RefreshAttemptsView.swift
|
||||||
|
// SideStore
|
||||||
|
//
|
||||||
|
// Created by Fabian Thies on 04.02.23.
|
||||||
|
// Copyright © 2023 SideStore. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import AltStoreCore
|
||||||
|
|
||||||
|
struct RefreshAttemptsView: View {
|
||||||
|
@SwiftUI.FetchRequest(sortDescriptors: [
|
||||||
|
NSSortDescriptor(keyPath: \RefreshAttempt.date, ascending: false)
|
||||||
|
])
|
||||||
|
var refreshAttempts: FetchedResults<RefreshAttempt>
|
||||||
|
|
||||||
|
var groupedRefreshAttempts: [Date: [RefreshAttempt]] {
|
||||||
|
Dictionary(grouping: refreshAttempts, by: { Calendar.current.startOfDay(for: $0.date) })
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
List {
|
||||||
|
ForEach(groupedRefreshAttempts.keys.sorted(by: { $0 > $1 }), id: \.self) { date in
|
||||||
|
Section {
|
||||||
|
let attempts = groupedRefreshAttempts[date] ?? []
|
||||||
|
ForEach(attempts, id: \.date) { attempt in
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
HStack {
|
||||||
|
if attempt.isSuccess {
|
||||||
|
Text("Success")
|
||||||
|
.bold()
|
||||||
|
.foregroundColor(.green)
|
||||||
|
} else {
|
||||||
|
Text("Failure")
|
||||||
|
.bold()
|
||||||
|
.foregroundColor(.red)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text(DateFormatterHelper.timeString(for: attempt.date))
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let description = attempt.errorDescription {
|
||||||
|
Text(description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text(DateFormatterHelper.string(for: date))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(self.listBackground)
|
||||||
|
.navigationTitle("Refresh Attempts")
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
var listBackground: some View {
|
||||||
|
if self.refreshAttempts.isEmpty {
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
Spacer()
|
||||||
|
Text("No Refresh Attempts")
|
||||||
|
.font(.title)
|
||||||
|
|
||||||
|
Text("The more you use SideStore, the more often iOS will allow it to refresh apps in the background.")
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.padding()
|
||||||
|
} else {
|
||||||
|
Color.clear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct RefreshAttemptsView_Previews: PreviewProvider {
|
||||||
|
|
||||||
|
static var previews: some View {
|
||||||
|
NavigationView {
|
||||||
|
RefreshAttemptsView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,8 @@ struct SettingsView: View {
|
|||||||
|
|
||||||
@State var isShowingConnectAppleIDView = false
|
@State var isShowingConnectAppleIDView = false
|
||||||
@State var isShowingAddShortcutView = false
|
@State var isShowingAddShortcutView = false
|
||||||
|
@State var isShowingFeedbackMailView = false
|
||||||
|
@State var isShowingResetPairingFileConfirmation = false
|
||||||
|
|
||||||
@State var externalURLToShow: URL?
|
@State var externalURLToShow: URL?
|
||||||
|
|
||||||
@@ -147,18 +149,45 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
SwiftUI.Button(action: switchToUIKit) {
|
NavigationLink("Show Error Log") {
|
||||||
Text(L10n.SettingsView.switchToUIKit)
|
|
||||||
}
|
|
||||||
|
|
||||||
SwiftUI.Button(action: resetImageCache) {
|
|
||||||
Text(L10n.SettingsView.resetImageCache)
|
|
||||||
}
|
|
||||||
|
|
||||||
NavigationLink {
|
|
||||||
ErrorLogView()
|
ErrorLogView()
|
||||||
} label: {
|
}
|
||||||
Text("Show Error Log")
|
|
||||||
|
NavigationLink("Show Refresh Attempts") {
|
||||||
|
RefreshAttemptsView()
|
||||||
|
}
|
||||||
|
|
||||||
|
if MailComposeView.canSendMail {
|
||||||
|
SwiftUI.Button("Send Feedback") {
|
||||||
|
self.isShowingFeedbackMailView = true
|
||||||
|
}
|
||||||
|
.sheet(isPresented: self.$isShowingFeedbackMailView) {
|
||||||
|
MailComposeView(recipients: ["support@sidestore.io"],
|
||||||
|
subject: "SideStore Beta \(appVersion) Feedback") {
|
||||||
|
NotificationManager.shared.showNotification(title: "Thank you for your feedback!")
|
||||||
|
} onError: { error in
|
||||||
|
NotificationManager.shared.reportError(error: error)
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwiftUI.Button(L10n.SettingsView.switchToUIKit, action: self.switchToUIKit)
|
||||||
|
|
||||||
|
SwiftUI.Button("Advanced Settings", action: self.showAdvancedSettings)
|
||||||
|
|
||||||
|
SwiftUI.Button(L10n.SettingsView.resetImageCache, action: self.resetImageCache)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
|
||||||
|
SwiftUI.Button("Reset Pairing File") {
|
||||||
|
self.isShowingResetPairingFileConfirmation = true
|
||||||
|
}
|
||||||
|
.foregroundColor(.red)
|
||||||
|
.actionSheet(isPresented: self.$isShowingResetPairingFileConfirmation) {
|
||||||
|
ActionSheet(title: Text("Are you sure to reset the pairing file?"), message: Text("You can reset the pairing file when you cannot sideload apps or enable JIT. SideStore will close when the file has been deleted."), buttons: [
|
||||||
|
.destructive(Text("Delete and Reset"), action: self.resetPairingFile),
|
||||||
|
.cancel()
|
||||||
|
])
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text(L10n.SettingsView.debug)
|
Text(L10n.SettingsView.debug)
|
||||||
@@ -231,7 +260,6 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func switchToUIKit() {
|
func switchToUIKit() {
|
||||||
let storyboard = UIStoryboard(name: "Main", bundle: .main)
|
let storyboard = UIStoryboard(name: "Main", bundle: .main)
|
||||||
let rootVC = storyboard.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController
|
let rootVC = storyboard.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController
|
||||||
@@ -251,6 +279,37 @@ struct SettingsView: View {
|
|||||||
fatalError("\(error)")
|
fatalError("\(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resetPairingFile() {
|
||||||
|
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||||
|
let fileURL = FileManager.default.documentsDirectory.appendingPathComponent(filename)
|
||||||
|
|
||||||
|
// Delete the pairing file if it exists
|
||||||
|
if FileManager.default.fileExists(atPath: fileURL.path) {
|
||||||
|
do {
|
||||||
|
try FileManager.default.removeItem(at: fileURL)
|
||||||
|
print("Pairing file deleted successfully.")
|
||||||
|
} catch {
|
||||||
|
print("Failed to delete pairing file:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close and exit SideStore
|
||||||
|
UIApplication.shared.perform(#selector(URLSessionTask.suspend))
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now().advanced(by: .milliseconds(500))) {
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showAdvancedSettings() {
|
||||||
|
// Create the URL that deep links to our app's custom settings.
|
||||||
|
guard let url = URL(string: UIApplication.openSettingsURLString) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask the system to open that URL.
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SettingsView_Previews: PreviewProvider {
|
struct SettingsView_Previews: PreviewProvider {
|
||||||
|
|||||||
Reference in New Issue
Block a user