diff --git a/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileAttributesView.swift b/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileAttributesView.swift new file mode 100644 index 00000000..c964c475 --- /dev/null +++ b/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileAttributesView.swift @@ -0,0 +1,124 @@ +// +// ProfileAttributesView.swift +// Passepartout +// +// Created by Davide De Rosa on 11/10/24. +// Copyright (c) 2024 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import SwiftUI + +struct ProfileAttributesView: View { + enum Attribute { + case shared + + case tv + } + + let attributes: [Attribute] + + let isRemoteImportingEnabled: Bool + + var body: some View { + if !attributes.isEmpty { + ZStack(alignment: .centerFirstTextBaseline) { + Group { + ThemeImage(.cloudOn) + ThemeImage(.cloudOff) + ThemeImage(.tvOn) + ThemeImage(.tvOff) + } + .hidden() + + HStack(alignment: .firstTextBaseline) { + ForEach(imageModels, id: \.name) { + ThemeImage($0.name) + .help($0.help) + } + } + } + .foregroundStyle(.secondary) + } + } + + var imageModels: [(name: Theme.ImageName, help: String)] { + attributes.map { + switch $0 { + case .shared: + return ( + isRemoteImportingEnabled ? .cloudOn : .cloudOff, + Strings.Modules.General.Rows.shared + ) + + case .tv: + return ( + isRemoteImportingEnabled ? .tvOn : .tvOff, + Strings.Modules.General.Rows.appleTv(Strings.Unlocalized.appleTV) + ) + } + } + } +} + +#Preview { + struct ContentView: View { + + @State + private var isRemoteImportingEnabled = false + + let timer = Timer.publish(every: 1, on: .main, in: .common) + .autoconnect() + + var body: some View { + ProfileAttributesView( + attributes: [.shared, .tv], + isRemoteImportingEnabled: isRemoteImportingEnabled + ) + .onReceive(timer) { _ in + isRemoteImportingEnabled.toggle() + } + .border(.black) + .padding() + .withMockEnvironment() + } + } + + return ContentView() +} + +#Preview("Row Alignment") { + IconsPreview() + .withMockEnvironment() +} + +struct IconsPreview: View { + var body: some View { + Form { + HStack(alignment: .firstTextBaseline) { + ThemeImage(.cloudOn) + ThemeImage(.cloudOff) + ThemeImage(.tvOn) + ThemeImage(.tvOff) + ThemeImage(.info) + } + } + .themeForm() + } +} diff --git a/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileRowView.swift b/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileRowView.swift index f5eb9f04..7c9f028f 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileRowView.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/App/ProfileRowView.swift @@ -63,7 +63,10 @@ struct ProfileRowView: View, Routable { } Spacer() HStack(spacing: 10.0) { - attributesView + ProfileAttributesView( + attributes: attributes, + isRemoteImportingEnabled: profileManager.isRemoteImportingEnabled + ) ProfileInfoButton(header: header) { flow?.onEditProfile($0) } @@ -131,20 +134,14 @@ private extension ProfileRowView { ) .foregroundStyle(.primary) } -} -// MARK: - Attributes - -private extension ProfileRowView { - var attributesView: some View { - Group { - if isTV { - tvImage - } else if isShared { - sharedImage - } + var attributes: [ProfileAttributesView.Attribute] { + if isTV { + return [.tv] + } else if isShared { + return [.shared] } - .foregroundStyle(.secondary) + return [] } var isShared: Bool { @@ -154,14 +151,34 @@ private extension ProfileRowView { var isTV: Bool { isShared && profileManager.isAvailableForTV(profileWithId: header.id) } - - var sharedImage: some View { - ThemeImage(profileManager.isRemoteImportingEnabled ? .cloudOn : .cloudOff) - .help(Strings.Modules.General.Rows.shared) - } - - var tvImage: some View { - ThemeImage(profileManager.isRemoteImportingEnabled ? .tvOn : .tvOff) - .help(Strings.Modules.General.Rows.appleTv(Strings.Unlocalized.appleTV)) - } +} + +// MARK: - Previews + +#Preview { + let profile: Profile = .mock + let profileManager: ProfileManager = .mock + + return Form { + ProfileRowView( + style: .compact, + profileManager: profileManager, + tunnel: .mock, + header: profile.header(), + interactiveManager: InteractiveManager(), + errorHandler: .default(), + nextProfileId: .constant(nil), + withMarker: true + ) + } + .task { + do { + try await profileManager.observeRemote(true) + try await profileManager.save(profile, force: true, remotelyShared: true) + } catch { + fatalError(error.localizedDescription) + } + } + .themeForm() + .withMockEnvironment() } diff --git a/Passepartout/Library/Sources/CommonLibrary/Business/ProfileManager.swift b/Passepartout/Library/Sources/CommonLibrary/Business/ProfileManager.swift index bba4f100..f64f5ec4 100644 --- a/Passepartout/Library/Sources/CommonLibrary/Business/ProfileManager.swift +++ b/Passepartout/Library/Sources/CommonLibrary/Business/ProfileManager.swift @@ -73,7 +73,9 @@ public final class ProfileManager: ObservableObject { public init(profiles: [Profile]) { repository = InMemoryProfileRepository(profiles: profiles) backupRepository = nil - remoteRepositoryBlock = nil + remoteRepositoryBlock = { _ in + InMemoryProfileRepository() + } mirrorsRemoteRepository = false processor = nil self.profiles = []