passepartout-apple/Passepartout/App/Views/OrganizerView+Profiles.swift
Davide De Rosa c9dfed676e Move VPN status below active profile
Rather than on the side. Similar to old subtitle table view cell.

Restore data count as well (when available).
2022-04-22 09:52:10 +02:00

168 lines
5.3 KiB
Swift

//
// OrganizerView+Profiles.swift
// Passepartout
//
// Created by Davide De Rosa on 4/2/22.
// Copyright (c) 2022 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 <http://www.gnu.org/licenses/>.
//
import SwiftUI
import PassepartoutCore
extension OrganizerView {
struct ProfilesList: View {
@ObservedObject private var appManager: AppManager
@ObservedObject private var profileManager: ProfileManager
@ObservedObject private var providerManager: ProviderManager
// just to observe changes in profiles eligibility
@ObservedObject private var productManager: ProductManager
@Binding private var alertType: AlertType?
@State private var isFirstLaunch = true
@State private var selectedProfileId: UUID?
init(alertType: Binding<AlertType?>) {
appManager = .shared
profileManager = .shared
providerManager = .shared
productManager = .shared
_alertType = alertType
}
var body: some View {
debugChanges()
return Group {
mainView
if profileManager.headers.isEmpty {
emptyView
}
}.onAppear {
performMigrationsIfNeeded()
}.onChange(of: profileManager.headers) {
dismissSelectionIfDeleted(headers: $0)
}
// from AddProfileView
.onReceive(profileManager.didCreateProfile) {
selectedProfileId = $0.id
}
}
private var mainView: some View {
List {
Section {
ForEach(sortedHeaders, content: navigationLink(forHeader:))
.onDelete(perform: removeProfiles)
}
}.animation(.default, value: profileManager.headers)
}
// FIXME: l10n
private var emptyView: some View {
VStack {
Text("No profiles")
.themeInformativeText()
}
}
private func navigationLink(forHeader header: Profile.Header) -> some View {
NavigationLink(tag: header.id, selection: $selectedProfileId) {
ProfileView(header: header)
} label: {
ProfileHeaderRow(
header: header,
isActive: profileManager.isActiveProfile(header.id)
)
}.onAppear {
preselectIfActiveProfile(header.id)
// XXX: iOS 14 bug, if selectedProfileId is set before its NavigationLink
// has appeared, the NavigationLink will not auto-activate once appeared
// enforce activation by clearing and resetting selectedProfileId to its
// current value
withAnimation {
if let tmp = selectedProfileId, tmp == header.id {
selectedProfileId = nil
selectedProfileId = tmp
}
}
}
}
}
}
extension OrganizerView.ProfilesList {
private var sortedHeaders: [Profile.Header] {
profileManager.headers.sorted()
}
private func preselectIfActiveProfile(_ id: UUID) {
// do not push profile if:
//
// - an alert is active, as it would break navigation
// - on iPad, as it's already shown
//
guard alertType == nil, themeIdiom != .pad, id == profileManager.activeHeader?.id else {
return
}
guard isFirstLaunch else {
return
}
isFirstLaunch = false
selectedProfileId = id
}
private func performMigrationsIfNeeded() {
Task {
await appManager.doMigrations(profileManager)
}
}
private func removeProfiles(_ indexSet: IndexSet) {
let currentHeaders = sortedHeaders
var toDelete: [UUID] = []
indexSet.forEach {
toDelete.append(currentHeaders[$0].id)
}
// clear selection before removal to avoid triggering a bogus navigation push
if let selectedProfileId = selectedProfileId, toDelete.contains(selectedProfileId) {
self.selectedProfileId = nil
}
profileManager.removeProfiles(withIds: toDelete)
}
private func dismissSelectionIfDeleted(headers: [Profile.Header]) {
if let selectedProfileId = selectedProfileId,
!profileManager.isExistingProfile(withId: selectedProfileId) {
self.selectedProfileId = nil
}
}
}