mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Add import on tvOS, other export/import improvements
This commit is contained in:
parent
0216c17b95
commit
e6deb9ef26
@ -2,15 +2,11 @@ import Defaults
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
|
||||||
struct ImportSettingsFileModel {
|
final class ImportSettingsFileModel: ObservableObject {
|
||||||
let url: URL
|
static let shared = ImportSettingsFileModel()
|
||||||
|
|
||||||
var filename: String {
|
|
||||||
String(url.lastPathComponent.dropLast(ImportExportSettingsModel.settingsExtension.count + 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
var locationsSettingsGroupImporter: LocationsSettingsGroupImporter? {
|
var locationsSettingsGroupImporter: LocationsSettingsGroupImporter? {
|
||||||
if let locationsSettings = json?.dictionaryValue["locationsSettings"] {
|
if let locationsSettings = json.dictionaryValue["locationsSettings"] {
|
||||||
return LocationsSettingsGroupImporter(
|
return LocationsSettingsGroupImporter(
|
||||||
json: locationsSettings,
|
json: locationsSettings,
|
||||||
includePublicLocations: importExportModel.isGroupEnabled(.locationsSettings),
|
includePublicLocations: importExportModel.isGroupEnabled(.locationsSettings),
|
||||||
@ -25,6 +21,8 @@ struct ImportSettingsFileModel {
|
|||||||
var importExportModel = ImportExportSettingsModel.shared
|
var importExportModel = ImportExportSettingsModel.shared
|
||||||
var sheetViewModel = ImportSettingsSheetViewModel.shared
|
var sheetViewModel = ImportSettingsSheetViewModel.shared
|
||||||
|
|
||||||
|
var loadTask: URLSessionTask?
|
||||||
|
|
||||||
func isGroupIncludedInFile(_ group: ImportExportSettingsModel.ExportGroup) -> Bool {
|
func isGroupIncludedInFile(_ group: ImportExportSettingsModel.ExportGroup) -> Bool {
|
||||||
switch group {
|
switch group {
|
||||||
case .locationsSettings:
|
case .locationsSettings:
|
||||||
@ -48,7 +46,7 @@ struct ImportSettingsFileModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func groupJSON(_ group: ImportExportSettingsModel.ExportGroup) -> JSON {
|
func groupJSON(_ group: ImportExportSettingsModel.ExportGroup) -> JSON {
|
||||||
json?.dictionaryValue[group.rawValue] ?? .init()
|
json.dictionaryValue[group.rawValue] ?? .init()
|
||||||
}
|
}
|
||||||
|
|
||||||
func performImport() {
|
func performImport() {
|
||||||
@ -91,17 +89,34 @@ struct ImportSettingsFileModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var json: JSON? {
|
@Published var json = JSON()
|
||||||
if let fileContents = try? Data(contentsOf: url),
|
|
||||||
let json = try? JSON(data: fileContents)
|
func loadData(_ url: URL) {
|
||||||
{
|
json = JSON()
|
||||||
return json
|
loadTask?.cancel()
|
||||||
|
|
||||||
|
loadTask = URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
|
||||||
|
guard let data else { return }
|
||||||
|
|
||||||
|
if let json = try? JSON(data: data) {
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
self.json = json
|
||||||
|
|
||||||
|
self.sheetViewModel.reset(locationsSettingsGroupImporter)
|
||||||
|
self.importExportModel.reset(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
loadTask?.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
func filename(_ url: URL) -> String {
|
||||||
|
String(url.lastPathComponent.dropLast(ImportExportSettingsModel.settingsExtension.count + 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
var metadataBuild: String? {
|
var metadataBuild: String? {
|
||||||
if let build = json?.dictionaryValue["metadata"]?.dictionaryValue["build"]?.string {
|
if let build = json.dictionaryValue["metadata"]?.dictionaryValue["build"]?.string {
|
||||||
return build
|
return build
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +124,7 @@ struct ImportSettingsFileModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var metadataPlatform: String? {
|
var metadataPlatform: String? {
|
||||||
if let platform = json?.dictionaryValue["metadata"]?.dictionaryValue["platform"]?.string {
|
if let platform = json.dictionaryValue["metadata"]?.dictionaryValue["platform"]?.string {
|
||||||
return platform
|
return platform
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +132,7 @@ struct ImportSettingsFileModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var metadataDate: String? {
|
var metadataDate: String? {
|
||||||
if let timestamp = json?.dictionaryValue["metadata"]?.dictionaryValue["timestamp"]?.doubleValue {
|
if let timestamp = json.dictionaryValue["metadata"]?.dictionaryValue["timestamp"]?.doubleValue {
|
||||||
let date = Date(timeIntervalSince1970: timestamp)
|
let date = Date(timeIntervalSince1970: timestamp)
|
||||||
return dateFormatter.string(from: date)
|
return dateFormatter.string(from: date)
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,6 @@ struct OpenURLHandler {
|
|||||||
var navigationStyle: NavigationStyle
|
var navigationStyle: NavigationStyle
|
||||||
|
|
||||||
func handle(_ url: URL) {
|
func handle(_ url: URL) {
|
||||||
if url.isFileURL, url.standardizedFileURL.absoluteString.hasSuffix(".\(ImportExportSettingsModel.settingsExtension)") {
|
|
||||||
navigation.presentSettingsImportSheet(url)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if Self.firstHandle {
|
if Self.firstHandle {
|
||||||
Self.firstHandle = false
|
Self.firstHandle = false
|
||||||
|
|
||||||
@ -26,6 +21,11 @@ struct OpenURLHandler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if url.isFileURL, url.standardizedFileURL.absoluteString.hasSuffix(".\(ImportExportSettingsModel.settingsExtension)") {
|
||||||
|
navigation.presentSettingsImportSheet(url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if accounts.current.isNil {
|
if accounts.current.isNil {
|
||||||
accounts.setCurrent(accounts.any)
|
accounts.setCurrent(accounts.any)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,13 @@ struct ExportSettings: View {
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#if os(iOS)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .confirmationAction) {
|
||||||
|
exportButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
.navigationTitle("Export Settings")
|
.navigationTitle("Export Settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,12 +113,6 @@ struct ExportSettings: View {
|
|||||||
ExportGroupRow(group: group)
|
ExportGroupRow(group: group)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(macOS)
|
|
||||||
Section {
|
|
||||||
exportButton
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.disabled(model.isExportInProgress)
|
.disabled(model.isExportInProgress)
|
||||||
@ -119,7 +120,7 @@ struct ExportSettings: View {
|
|||||||
|
|
||||||
var exportButton: some View {
|
var exportButton: some View {
|
||||||
Button(action: exportSettings) {
|
Button(action: exportSettings) {
|
||||||
Label(model.isExportInProgress ? "Export in progress..." : "Export...", systemImage: model.isExportInProgress ? "fireworks" : "square.and.arrow.up")
|
Text(model.isExportInProgress ? "In progress..." : "Export")
|
||||||
.animation(nil, value: model.isExportInProgress)
|
.animation(nil, value: model.isExportInProgress)
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
|
34
Shared/Settings/Import/ImportSettings.swift
Normal file
34
Shared/Settings/Import/ImportSettings.swift
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
@ -27,102 +27,110 @@ struct ImportSettingsAccountRow: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Button(action: { model.toggleAccount(account, accounts: accounts) }) {
|
#if os(tvOS)
|
||||||
let accountExists = AccountsModel.shared.find(account.id) != nil
|
row
|
||||||
|
#else
|
||||||
|
Button(action: { model.toggleAccount(account, accounts: accounts) }) {
|
||||||
|
row
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
var row: some View {
|
||||||
HStack {
|
let accountExists = AccountsModel.shared.find(account.id) != nil
|
||||||
Text(account.username)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "checkmark")
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
.opacity(isChecked ? 1 : 0)
|
|
||||||
}
|
|
||||||
Text(account.instance?.description ?? "")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
|
|
||||||
Group {
|
return VStack(alignment: .leading) {
|
||||||
if let instanceID = account.instanceID {
|
HStack {
|
||||||
if accountExists {
|
Text(account.username)
|
||||||
HStack {
|
Spacer()
|
||||||
Image(systemName: "xmark.circle.fill")
|
Image(systemName: "checkmark")
|
||||||
.foregroundColor(Color("AppRedColor"))
|
.foregroundColor(.accentColor)
|
||||||
Text("Account already exists")
|
.opacity(isChecked ? 1 : 0)
|
||||||
}
|
}
|
||||||
} else {
|
Text(account.instance?.description ?? "")
|
||||||
Group {
|
.font(.caption)
|
||||||
if InstancesModel.shared.find(instanceID) != nil {
|
.foregroundColor(.secondary)
|
||||||
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)
|
|
||||||
|
|
||||||
if account.password.isNil || account.password!.isEmpty {
|
Group {
|
||||||
Group {
|
if let instanceID = account.instanceID {
|
||||||
if password.isEmpty {
|
if accountExists {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "key")
|
Image(systemName: "xmark.circle.fill")
|
||||||
Text("Password required to import")
|
.foregroundColor(Color("AppRedColor"))
|
||||||
}
|
Text("Account already exists")
|
||||||
.foregroundColor(Color("AppRedColor"))
|
}
|
||||||
} else {
|
} else {
|
||||||
AccountValidationStatus(
|
Group {
|
||||||
app: .constant(instance.app),
|
if InstancesModel.shared.find(instanceID) != nil {
|
||||||
isValid: $isValid,
|
|
||||||
isValidated: $isValidated,
|
|
||||||
isValidating: $isValidating,
|
|
||||||
error: $validationError
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(minHeight: 20)
|
|
||||||
} else {
|
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "checkmark.circle.fill")
|
Image(systemName: "checkmark.circle.fill")
|
||||||
.foregroundColor(.green)
|
.foregroundColor(.green)
|
||||||
|
Text("Custom Location already exists")
|
||||||
Text("Password saved in import file")
|
|
||||||
}
|
}
|
||||||
|
} 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)
|
.foregroundColor(.primary)
|
||||||
.contentShape(Rectangle())
|
.font(.caption)
|
||||||
.onChange(of: isValid) { _ in afterValidation() }
|
.padding(.vertical, 2)
|
||||||
.animation(nil, value: isChecked)
|
|
||||||
|
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 {
|
var isChecked: Bool {
|
||||||
@ -173,7 +181,8 @@ struct ImportSettingsAccountRow: View {
|
|||||||
|
|
||||||
struct ImportSettingsAccountRow_Previews: PreviewProvider {
|
struct ImportSettingsAccountRow_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
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 {
|
return List {
|
||||||
ImportSettingsAccountRow(
|
ImportSettingsAccountRow(
|
||||||
|
@ -4,6 +4,7 @@ struct ImportSettingsSheetView: View {
|
|||||||
@Binding var settingsFile: URL?
|
@Binding var settingsFile: URL?
|
||||||
@StateObject private var model = ImportSettingsSheetViewModel.shared
|
@StateObject private var model = ImportSettingsSheetViewModel.shared
|
||||||
@StateObject private var importExportModel = ImportExportSettingsModel.shared
|
@StateObject private var importExportModel = ImportExportSettingsModel.shared
|
||||||
|
@StateObject private var fileModel = ImportSettingsFileModel.shared
|
||||||
|
|
||||||
@Environment(\.presentationMode) private var presentationMode
|
@Environment(\.presentationMode) private var presentationMode
|
||||||
|
|
||||||
@ -23,12 +24,12 @@ struct ImportSettingsSheetView: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
guard let fileModel else { return }
|
guard let settingsFile else { return }
|
||||||
model.reset(fileModel.locationsSettingsGroupImporter)
|
fileModel.loadData(settingsFile)
|
||||||
importExportModel.reset(fileModel)
|
|
||||||
}
|
}
|
||||||
.onChange(of: settingsFile) { _ in
|
.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) {
|
ToolbarItem(placement: .confirmationAction) {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
fileModel?.performImport()
|
fileModel.performImport()
|
||||||
presentingCompletedAlert = true
|
presentingCompletedAlert = true
|
||||||
ImportExportSettingsModel.shared.reset()
|
ImportExportSettingsModel.shared.reset()
|
||||||
}) {
|
}) {
|
||||||
@ -85,16 +86,8 @@ struct ImportSettingsSheetView: View {
|
|||||||
return !model.selectedAccounts.isEmpty || !model.selectedInstances.isEmpty || !importExportModel.selectedExportGroups.isEmpty
|
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? {
|
var locationsSettingsGroupImporter: LocationsSettingsGroupImporter? {
|
||||||
guard let fileModel else { return nil }
|
fileModel.locationsSettingsGroupImporter
|
||||||
|
|
||||||
return fileModel.locationsSettingsGroupImporter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ExportGroupRow: View {
|
struct ExportGroupRow: View {
|
||||||
@ -128,34 +121,43 @@ struct ImportSettingsSheetView: View {
|
|||||||
Section(header: Text("Settings")) {
|
Section(header: Text("Settings")) {
|
||||||
ForEach(ImportExportSettingsModel.ExportGroup.settingsGroups) { group in
|
ForEach(ImportExportSettingsModel.ExportGroup.settingsGroups) { group in
|
||||||
ExportGroupRow(group: group)
|
ExportGroupRow(group: group)
|
||||||
.disabled(!fileModel!.isGroupIncludedInFile(group))
|
.disabled(!fileModel.isGroupIncludedInFile(group))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section(header: Text("Other")) {
|
Section(header: Text("Other")) {
|
||||||
ForEach(ImportExportSettingsModel.ExportGroup.otherGroups) { group in
|
ForEach(ImportExportSettingsModel.ExportGroup.otherGroups) { group in
|
||||||
ExportGroupRow(group: group)
|
ExportGroupRow(group: group)
|
||||||
.disabled(!fileModel!.isGroupIncludedInFile(group))
|
.disabled(!fileModel.isGroupIncludedInFile(group))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder var metadata: some View {
|
@ViewBuilder var metadata: some View {
|
||||||
if let fileModel {
|
if let settingsFile {
|
||||||
Section(header: Text("File information")) {
|
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 {
|
if let date = fileModel.metadataDate {
|
||||||
MetadataRow(name: Text("Date"), value: Text(date))
|
MetadataRow(name: Text("Date"), value: Text(date))
|
||||||
|
#if os(tvOS)
|
||||||
|
.focusable()
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if let build = fileModel.metadataBuild {
|
if let build = fileModel.metadataBuild {
|
||||||
MetadataRow(name: Text("Build"), value: Text(build))
|
MetadataRow(name: Text("Build"), value: Text(build))
|
||||||
|
#if os(tvOS)
|
||||||
|
.focusable()
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if let platform = fileModel.metadataPlatform {
|
if let platform = fileModel.metadataPlatform {
|
||||||
MetadataRow(name: Text("Platform"), value: Text(platform))
|
MetadataRow(name: Text("Platform"), value: Text(platform))
|
||||||
|
#if os(tvOS)
|
||||||
|
.focusable()
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,24 +233,22 @@ struct ImportSettingsSheetView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder var importOptions: some View {
|
@ViewBuilder var importOptions: some View {
|
||||||
if let fileModel {
|
if fileModel.isPublicInstancesSettingsGroupInFile || !instances.isEmpty {
|
||||||
if fileModel.isPublicInstancesSettingsGroupInFile || !instances.isEmpty {
|
Section(header: Text("Locations")) {
|
||||||
Section(header: Text("Locations")) {
|
if fileModel.isPublicInstancesSettingsGroupInFile {
|
||||||
if fileModel.isPublicInstancesSettingsGroupInFile {
|
ExportGroupRow(group: .locationsSettings)
|
||||||
ExportGroupRow(group: .locationsSettings)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ForEach(instances) { instance in
|
ForEach(instances) { instance in
|
||||||
ImportInstanceRow(instance: instance, accounts: accounts)
|
ImportInstanceRow(instance: instance, accounts: accounts)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !accounts.isEmpty {
|
if !accounts.isEmpty {
|
||||||
Section(header: Text("Accounts")) {
|
Section(header: Text("Accounts")) {
|
||||||
ForEach(accounts) { account in
|
ForEach(accounts) { account in
|
||||||
ImportSettingsAccountRow(account: account, fileModel: fileModel)
|
ImportSettingsAccountRow(account: account, fileModel: fileModel)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,9 +31,9 @@ struct SettingsView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
settings
|
settings
|
||||||
|
.modifier(ImportSettingsSheetViewModifier(isPresented: $settingsModel.presentingSettingsImportSheet, settingsFile: $settingsModel.settingsImportURL))
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
.modifier(ImportSettingsFileImporterViewModifier(isPresented: $navigation.presentingSettingsFileImporter))
|
.modifier(ImportSettingsFileImporterViewModifier(isPresented: $navigation.presentingSettingsFileImporter))
|
||||||
.modifier(ImportSettingsSheetViewModifier(isPresented: $settingsModel.presentingSettingsImportSheet, settingsFile: $settingsModel.settingsImportURL))
|
|
||||||
#endif
|
#endif
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.backport
|
.backport
|
||||||
@ -281,19 +281,27 @@ struct SettingsView: View {
|
|||||||
|
|
||||||
var importView: some View {
|
var importView: some View {
|
||||||
Section {
|
Section {
|
||||||
Button(action: importSettings) {
|
#if os(tvOS)
|
||||||
Label("Import Settings...", systemImage: "square.and.arrow.down")
|
NavigationLink(destination: LazyView(ImportSettings())) {
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
Label("Import Settings", systemImage: "square.and.arrow.down")
|
||||||
.contentShape(Rectangle())
|
.labelStyle(SettingsLabel())
|
||||||
}
|
}
|
||||||
.foregroundColor(.accentColor)
|
.padding(.horizontal, 20)
|
||||||
.buttonStyle(.plain)
|
#else
|
||||||
|
Button(action: importSettings) {
|
||||||
|
Label("Import Settings...", systemImage: "square.and.arrow.down")
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
}
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
|
||||||
NavigationLink(destination: LazyView(ExportSettings())) {
|
NavigationLink(destination: LazyView(ExportSettings())) {
|
||||||
Label("Export Settings", systemImage: "square.and.arrow.up")
|
Label("Export Settings", systemImage: "square.and.arrow.up")
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,6 +662,7 @@
|
|||||||
37A5DBC8285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */; };
|
37A5DBC8285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */; };
|
||||||
37A5DBC9285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */; };
|
37A5DBC9285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */; };
|
||||||
37A5DBCA285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */; };
|
37A5DBCA285E371400CA4DD1 /* ControlBackgroundModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */; };
|
||||||
|
37A6D4ED2B6E372700B26299 /* ImportSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A6D4EC2B6E372700B26299 /* ImportSettings.swift */; };
|
||||||
37A7D6E32B67E303009CB1ED /* ImportSettingsFileModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372C74692B67098A00BE179B /* ImportSettingsFileModel.swift */; };
|
37A7D6E32B67E303009CB1ED /* ImportSettingsFileModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372C74692B67098A00BE179B /* ImportSettingsFileModel.swift */; };
|
||||||
37A7D6E52B67E315009CB1ED /* SettingsGroupExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A7D6E42B67E315009CB1ED /* SettingsGroupExporter.swift */; };
|
37A7D6E52B67E315009CB1ED /* SettingsGroupExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A7D6E42B67E315009CB1ED /* SettingsGroupExporter.swift */; };
|
||||||
37A7D6E62B67E315009CB1ED /* SettingsGroupExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A7D6E42B67E315009CB1ED /* SettingsGroupExporter.swift */; };
|
37A7D6E62B67E315009CB1ED /* SettingsGroupExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A7D6E42B67E315009CB1ED /* SettingsGroupExporter.swift */; };
|
||||||
@ -1360,6 +1361,7 @@
|
|||||||
37A362BD29537AAA00BDF328 /* PlaybackSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackSettings.swift; sourceTree = "<group>"; };
|
37A362BD29537AAA00BDF328 /* PlaybackSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackSettings.swift; sourceTree = "<group>"; };
|
||||||
37A362C129537FED00BDF328 /* PlaybackSettingsPresentationDetents+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlaybackSettingsPresentationDetents+Backport.swift"; sourceTree = "<group>"; };
|
37A362C129537FED00BDF328 /* PlaybackSettingsPresentationDetents+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlaybackSettingsPresentationDetents+Backport.swift"; sourceTree = "<group>"; };
|
||||||
37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlBackgroundModifier.swift; sourceTree = "<group>"; };
|
37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlBackgroundModifier.swift; sourceTree = "<group>"; };
|
||||||
|
37A6D4EC2B6E372700B26299 /* ImportSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportSettings.swift; sourceTree = "<group>"; };
|
||||||
37A7D6E42B67E315009CB1ED /* SettingsGroupExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsGroupExporter.swift; sourceTree = "<group>"; };
|
37A7D6E42B67E315009CB1ED /* SettingsGroupExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsGroupExporter.swift; sourceTree = "<group>"; };
|
||||||
37A7D6E82B67E334009CB1ED /* BrowsingSettingsGroupExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowsingSettingsGroupExporter.swift; sourceTree = "<group>"; };
|
37A7D6E82B67E334009CB1ED /* BrowsingSettingsGroupExporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowsingSettingsGroupExporter.swift; sourceTree = "<group>"; };
|
||||||
37A7D6EC2B67E3BF009CB1ED /* BrowsingSettingsGroupImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowsingSettingsGroupImporter.swift; sourceTree = "<group>"; };
|
37A7D6EC2B67E3BF009CB1ED /* BrowsingSettingsGroupImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowsingSettingsGroupImporter.swift; sourceTree = "<group>"; };
|
||||||
@ -2162,6 +2164,7 @@
|
|||||||
37BBB33D2B6B9C80001C4845 /* Import */ = {
|
37BBB33D2B6B9C80001C4845 /* Import */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
37A6D4EC2B6E372700B26299 /* ImportSettings.swift */,
|
||||||
37BBB3422B6BB88F001C4845 /* ImportSettingsAccountRow.swift */,
|
37BBB3422B6BB88F001C4845 /* ImportSettingsAccountRow.swift */,
|
||||||
372C74622B66FFFC00BE179B /* ImportSettingsFileImporterViewModifier.swift */,
|
372C74622B66FFFC00BE179B /* ImportSettingsFileImporterViewModifier.swift */,
|
||||||
37BBB33E2B6B9D52001C4845 /* ImportSettingsSheetView.swift */,
|
37BBB33E2B6B9D52001C4845 /* ImportSettingsSheetView.swift */,
|
||||||
@ -3840,6 +3843,7 @@
|
|||||||
37E80F45287B7AC000561799 /* ControlsBar.swift in Sources */,
|
37E80F45287B7AC000561799 /* ControlsBar.swift in Sources */,
|
||||||
3743CA50270EFE3400E4D32B /* PlayerQueueRow.swift in Sources */,
|
3743CA50270EFE3400E4D32B /* PlayerQueueRow.swift in Sources */,
|
||||||
376BE50827347B57009AD608 /* SettingsHeader.swift in Sources */,
|
376BE50827347B57009AD608 /* SettingsHeader.swift in Sources */,
|
||||||
|
37A6D4ED2B6E372700B26299 /* ImportSettings.swift in Sources */,
|
||||||
37A9966026D6F9B9006E3224 /* HomeView.swift in Sources */,
|
37A9966026D6F9B9006E3224 /* HomeView.swift in Sources */,
|
||||||
372820402945E4A8009A0E2D /* SubscriptionsPageButton.swift in Sources */,
|
372820402945E4A8009A0E2D /* SubscriptionsPageButton.swift in Sources */,
|
||||||
37001565271B1F250049C794 /* AccountsModel.swift in Sources */,
|
37001565271B1F250049C794 /* AccountsModel.swift in Sources */,
|
||||||
|
Loading…
Reference in New Issue
Block a user