Add import on tvOS, other export/import improvements

This commit is contained in:
Arkadiusz Fal
2024-02-03 21:04:37 +01:00
parent 0216c17b95
commit e6deb9ef26
8 changed files with 230 additions and 159 deletions

View File

@@ -0,0 +1,34 @@
import SwiftUI
struct ImportSettings: View {
@State private var fileURL = ""
var body: some View {
VStack(spacing: 100) {
VStack(alignment: .leading, spacing: 20) {
Text("1. Export settings from Yattee for iOS or macOS")
Text("2. Upload it to a file hosting (e. g. Pastebin or GitHub Gist)")
Text("3. Enter file URL in the field below. You can use iOS remote to paste.")
}
TextField("URL", text: $fileURL)
Button {
if let url = URL(string: fileURL) {
NavigationModel.shared.presentSettingsImportSheet(url)
}
} label: {
Text("Import")
}
}
.padding(20)
.navigationTitle("Import Settings")
}
}
struct ImportSettings_Previews: PreviewProvider {
static var previews: some View {
ImportSettings()
}
}

View File

@@ -27,102 +27,110 @@ struct ImportSettingsAccountRow: View {
}
var body: some View {
Button(action: { model.toggleAccount(account, accounts: accounts) }) {
let accountExists = AccountsModel.shared.find(account.id) != nil
#if os(tvOS)
row
#else
Button(action: { model.toggleAccount(account, accounts: accounts) }) {
row
}
.buttonStyle(.plain)
#endif
}
VStack(alignment: .leading) {
HStack {
Text(account.username)
Spacer()
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
.opacity(isChecked ? 1 : 0)
}
Text(account.instance?.description ?? "")
.font(.caption)
.foregroundColor(.secondary)
var row: some View {
let accountExists = AccountsModel.shared.find(account.id) != nil
Group {
if let instanceID = account.instanceID {
if accountExists {
HStack {
Image(systemName: "xmark.circle.fill")
.foregroundColor(Color("AppRedColor"))
Text("Account already exists")
}
} else {
Group {
if InstancesModel.shared.find(instanceID) != nil {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("Custom Location already exists")
}
} else if model.selectedInstances.contains(instanceID) {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("Custom Location selected for import")
}
} else {
HStack {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.red)
Text("Custom Location not selected for import")
}
.foregroundColor(Color("AppRedColor"))
}
}
.frame(minHeight: 20)
return VStack(alignment: .leading) {
HStack {
Text(account.username)
Spacer()
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
.opacity(isChecked ? 1 : 0)
}
Text(account.instance?.description ?? "")
.font(.caption)
.foregroundColor(.secondary)
if account.password.isNil || account.password!.isEmpty {
Group {
if password.isEmpty {
HStack {
Image(systemName: "key")
Text("Password required to import")
}
.foregroundColor(Color("AppRedColor"))
} else {
AccountValidationStatus(
app: .constant(instance.app),
isValid: $isValid,
isValidated: $isValidated,
isValidating: $isValidating,
error: $validationError
)
}
}
.frame(minHeight: 20)
} else {
Group {
if let instanceID = account.instanceID {
if accountExists {
HStack {
Image(systemName: "xmark.circle.fill")
.foregroundColor(Color("AppRedColor"))
Text("Account already exists")
}
} else {
Group {
if InstancesModel.shared.find(instanceID) != nil {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("Password saved in import file")
Text("Custom Location already exists")
}
} else if model.selectedInstances.contains(instanceID) {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("Custom Location selected for import")
}
} else {
HStack {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.red)
Text("Custom Location not selected for import")
}
.foregroundColor(Color("AppRedColor"))
}
}
.frame(minHeight: 20)
if account.password.isNil || account.password!.isEmpty {
Group {
if password.isEmpty {
HStack {
Image(systemName: "key")
Text("Password required to import")
}
.foregroundColor(Color("AppRedColor"))
} else {
AccountValidationStatus(
app: .constant(instance.app),
isValid: $isValid,
isValidated: $isValidated,
isValidating: $isValidating,
error: $validationError
)
}
}
.frame(minHeight: 20)
} else {
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("Password saved in import file")
}
}
}
}
.foregroundColor(.primary)
.font(.caption)
.padding(.vertical, 2)
if !accountExists && (account.password.isNil || account.password!.isEmpty) {
SecureField("Password", text: $password)
.onChange(of: password) { _ in validate() }
#if !os(tvOS)
.textFieldStyle(RoundedBorderTextFieldStyle())
#endif
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
.onChange(of: isValid) { _ in afterValidation() }
.animation(nil, value: isChecked)
.foregroundColor(.primary)
.font(.caption)
.padding(.vertical, 2)
if !accountExists && (account.password.isNil || account.password!.isEmpty) {
SecureField("Password", text: $password)
.onChange(of: password) { _ in validate() }
#if !os(tvOS)
.textFieldStyle(RoundedBorderTextFieldStyle())
#endif
}
}
.buttonStyle(.plain)
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
.onChange(of: isValid) { _ in afterValidation() }
.animation(nil, value: isChecked)
}
var isChecked: Bool {
@@ -173,7 +181,8 @@ struct ImportSettingsAccountRow: View {
struct ImportSettingsAccountRow_Previews: PreviewProvider {
static var previews: some View {
let fileModel = ImportSettingsFileModel(url: URL(string: "https://gist.githubusercontent.com/arekf/578668969c9fdef1b3828bea864c3956/raw/f794a95a20261bcb1145e656c8dda00bea339e2a/yattee-recents.yatteesettings")!)
let fileModel = ImportSettingsFileModel()
fileModel.loadData(URL(string: "https://gist.githubusercontent.com/arekf/578668969c9fdef1b3828bea864c3956/raw/f794a95a20261bcb1145e656c8dda00bea339e2a/yattee-recents.yatteesettings")!)
return List {
ImportSettingsAccountRow(

View File

@@ -4,6 +4,7 @@ struct ImportSettingsSheetView: View {
@Binding var settingsFile: URL?
@StateObject private var model = ImportSettingsSheetViewModel.shared
@StateObject private var importExportModel = ImportExportSettingsModel.shared
@StateObject private var fileModel = ImportSettingsFileModel.shared
@Environment(\.presentationMode) private var presentationMode
@@ -23,12 +24,12 @@ struct ImportSettingsSheetView: View {
#endif
}
.onAppear {
guard let fileModel else { return }
model.reset(fileModel.locationsSettingsGroupImporter)
importExportModel.reset(fileModel)
guard let settingsFile else { return }
fileModel.loadData(settingsFile)
}
.onChange(of: settingsFile) { _ in
importExportModel.reset(fileModel)
guard let settingsFile else { return }
fileModel.loadData(settingsFile)
}
}
@@ -56,7 +57,7 @@ struct ImportSettingsSheetView: View {
}
ToolbarItem(placement: .confirmationAction) {
Button(action: {
fileModel?.performImport()
fileModel.performImport()
presentingCompletedAlert = true
ImportExportSettingsModel.shared.reset()
}) {
@@ -85,16 +86,8 @@ struct ImportSettingsSheetView: View {
return !model.selectedAccounts.isEmpty || !model.selectedInstances.isEmpty || !importExportModel.selectedExportGroups.isEmpty
}
var fileModel: ImportSettingsFileModel? {
guard let settingsFile else { return nil }
return ImportSettingsFileModel(url: settingsFile)
}
var locationsSettingsGroupImporter: LocationsSettingsGroupImporter? {
guard let fileModel else { return nil }
return fileModel.locationsSettingsGroupImporter
fileModel.locationsSettingsGroupImporter
}
struct ExportGroupRow: View {
@@ -128,34 +121,43 @@ struct ImportSettingsSheetView: View {
Section(header: Text("Settings")) {
ForEach(ImportExportSettingsModel.ExportGroup.settingsGroups) { group in
ExportGroupRow(group: group)
.disabled(!fileModel!.isGroupIncludedInFile(group))
.disabled(!fileModel.isGroupIncludedInFile(group))
}
}
Section(header: Text("Other")) {
ForEach(ImportExportSettingsModel.ExportGroup.otherGroups) { group in
ExportGroupRow(group: group)
.disabled(!fileModel!.isGroupIncludedInFile(group))
.disabled(!fileModel.isGroupIncludedInFile(group))
}
}
}
}
@ViewBuilder var metadata: some View {
if let fileModel {
if let settingsFile {
Section(header: Text("File information")) {
MetadataRow(name: Text("Name"), value: Text(fileModel.filename))
MetadataRow(name: Text("Name"), value: Text(fileModel.filename(settingsFile)))
if let date = fileModel.metadataDate {
MetadataRow(name: Text("Date"), value: Text(date))
#if os(tvOS)
.focusable()
#endif
}
if let build = fileModel.metadataBuild {
MetadataRow(name: Text("Build"), value: Text(build))
#if os(tvOS)
.focusable()
#endif
}
if let platform = fileModel.metadataPlatform {
MetadataRow(name: Text("Platform"), value: Text(platform))
#if os(tvOS)
.focusable()
#endif
}
}
}
@@ -231,24 +233,22 @@ struct ImportSettingsSheetView: View {
}
@ViewBuilder var importOptions: some View {
if let fileModel {
if fileModel.isPublicInstancesSettingsGroupInFile || !instances.isEmpty {
Section(header: Text("Locations")) {
if fileModel.isPublicInstancesSettingsGroupInFile {
ExportGroupRow(group: .locationsSettings)
}
if fileModel.isPublicInstancesSettingsGroupInFile || !instances.isEmpty {
Section(header: Text("Locations")) {
if fileModel.isPublicInstancesSettingsGroupInFile {
ExportGroupRow(group: .locationsSettings)
}
ForEach(instances) { instance in
ImportInstanceRow(instance: instance, accounts: accounts)
}
ForEach(instances) { instance in
ImportInstanceRow(instance: instance, accounts: accounts)
}
}
}
if !accounts.isEmpty {
Section(header: Text("Accounts")) {
ForEach(accounts) { account in
ImportSettingsAccountRow(account: account, fileModel: fileModel)
}
if !accounts.isEmpty {
Section(header: Text("Accounts")) {
ForEach(accounts) { account in
ImportSettingsAccountRow(account: account, fileModel: fileModel)
}
}
}