Simplify AddProfileView with implicit animations

- Animate on ViewModel in profile name views

- Animate on providers in provider selection view
This commit is contained in:
Davide De Rosa 2022-04-21 15:08:40 +02:00
parent 36cd9cfd96
commit e71b22c7c8
5 changed files with 30 additions and 56 deletions

View File

@ -60,7 +60,7 @@ struct AddHostView: View {
} else { } else {
completeView completeView
} }
} }.animation(.default, value: viewModel)
// hidden // hidden
NavigationLink("", isActive: $isEnteringCredentials) { NavigationLink("", isActive: $isEnteringCredentials) {
@ -159,18 +159,13 @@ struct AddHostView: View {
title: Text(L10n.AddProfile.Shared.title), title: Text(L10n.AddProfile.Shared.title),
message: Text(L10n.AddProfile.Shared.Alerts.Overwrite.message), message: Text(L10n.AddProfile.Shared.Alerts.Overwrite.message),
primaryButton: .destructive(Text(L10n.Global.Strings.ok)) { primaryButton: .destructive(Text(L10n.Global.Strings.ok)) {
// XXX: delay withAnimation() to not overlap with alert dismiss animation
Task {
processProfile(replacingExisting: true) processProfile(replacingExisting: true)
}
}, },
secondaryButton: .cancel(Text(L10n.Global.Strings.cancel)) secondaryButton: .cancel(Text(L10n.Global.Strings.cancel))
) )
} }
private func processProfile(replacingExisting: Bool) { private func processProfile(replacingExisting: Bool) {
withAnimation {
viewModel.processURL( viewModel.processURL(
url, url,
with: profileManager, with: profileManager,
@ -178,12 +173,9 @@ struct AddHostView: View {
deletingURLOnSuccess: deletingURLOnSuccess deletingURLOnSuccess: deletingURLOnSuccess
) )
} }
}
private func saveProfile() { private func saveProfile() {
let result = withAnimation { let result = viewModel.addProcessedProfile(to: profileManager)
viewModel.addProcessedProfile(to: profileManager)
}
guard result else { guard result else {
return return
} }

View File

@ -29,7 +29,7 @@ import TunnelKitOpenVPN
import TunnelKitWireGuard import TunnelKitWireGuard
extension AddHostView { extension AddHostView {
struct ViewModel { struct ViewModel: Equatable {
private var isNamePreset = false private var isNamePreset = false
var profileName = "" var profileName = ""

View File

@ -96,24 +96,18 @@ extension AddProviderView {
title: Text(L10n.AddProfile.Shared.title), title: Text(L10n.AddProfile.Shared.title),
message: Text(L10n.AddProfile.Shared.Alerts.Overwrite.message), message: Text(L10n.AddProfile.Shared.Alerts.Overwrite.message),
primaryButton: .destructive(Text(L10n.Global.Strings.ok)) { primaryButton: .destructive(Text(L10n.Global.Strings.ok)) {
// XXX: delay withAnimation() to not overlap with alert dismiss animation
Task {
saveProfile(replacingExisting: true) saveProfile(replacingExisting: true)
}
}, },
secondaryButton: .cancel(Text(L10n.Global.Strings.cancel)) secondaryButton: .cancel(Text(L10n.Global.Strings.cancel))
) )
} }
private func saveProfile(replacingExisting: Bool) { private func saveProfile(replacingExisting: Bool) {
let addedProfile = withAnimation { let addedProfile = viewModel.addProfile(
viewModel.addProfile(
profile, profile,
to: profileManager, to: profileManager,
replacingExisting: replacingExisting replacingExisting: replacingExisting
) )
}
guard let addedProfile = addedProfile else { guard let addedProfile = addedProfile else {
return return
} }

View File

@ -41,9 +41,16 @@ struct AddProviderView: View {
self.bindings = bindings self.bindings = bindings
} }
private var providers: [ProviderMetadata] {
providerManager.allProviders()
.filter {
$0.supportedVPNProtocols.contains(viewModel.selectedVPNProtocol)
}.sorted()
}
private var availableVPNProtocols: [VPNProtocolType] { private var availableVPNProtocols: [VPNProtocolType] {
var protos: Set<VPNProtocolType> = [] var protos: Set<VPNProtocolType> = []
viewModel.providers.forEach { providers.forEach {
$0.supportedVPNProtocols.forEach { $0.supportedVPNProtocols.forEach {
protos.insert($0) protos.insert($0)
} }
@ -70,16 +77,17 @@ struct AddProviderView: View {
ScrollViewReader { scrollProxy in ScrollViewReader { scrollProxy in
List { List {
mainSection mainSection
if !viewModel.providers.isEmpty { if !providers.isEmpty {
providersSection providersSection
} }
}.onChange(of: viewModel.errorMessage) { }.onChange(of: viewModel.errorMessage) {
onErrorMessage($0, scrollProxy) onErrorMessage($0, scrollProxy)
}.disabled(viewModel.pendingOperation != nil) }.disabled(viewModel.pendingOperation != nil)
.animation(.default, value: providers)
} }
// hidden // hidden
ForEach(viewModel.providers, id: \.navigationId, content: providerNavigationLink) ForEach(providers, id: \.navigationId, content: providerNavigationLink)
}.themeSecondaryView() }.themeSecondaryView()
.navigationTitle(L10n.AddProfile.Shared.title) .navigationTitle(L10n.AddProfile.Shared.title)
.toolbar(content: toolbar) .toolbar(content: toolbar)
@ -87,12 +95,6 @@ struct AddProviderView: View {
NavigationView { NavigationView {
PaywallView(isPresented: $viewModel.isPaywallPresented) PaywallView(isPresented: $viewModel.isPaywallPresented)
}.themeGlobal() }.themeGlobal()
}.onAppear {
refreshProviders()
}.onChange(of: viewModel.newProviders) { newValue in
withAnimation {
refreshProviders(newValue)
}
} }
} }
@ -122,7 +124,7 @@ struct AddProviderView: View {
Section( Section(
footer: themeErrorMessage(viewModel.errorMessage) footer: themeErrorMessage(viewModel.errorMessage)
) { ) {
ForEach(viewModel.providers, content: providerRow) ForEach(providers, content: providerRow)
} }
} }
@ -150,13 +152,6 @@ struct AddProviderView: View {
}.withTrailingProgress(when: isUpdatingIndex) }.withTrailingProgress(when: isUpdatingIndex)
} }
private func refreshProviders(_ newProviders: [ProviderMetadata]? = nil) {
viewModel.providers = (newProviders ?? providerManager.allProviders())
.filter {
$0.supportedVPNProtocols.contains(viewModel.selectedVPNProtocol)
}.sorted()
}
// eligibility: select or purchase provider // eligibility: select or purchase provider
private func presentOrPurchaseProvider(_ metadata: ProviderMetadata) { private func presentOrPurchaseProvider(_ metadata: ProviderMetadata) {
if productManager.isEligible(forProvider: metadata.name) { if productManager.isEligible(forProvider: metadata.name) {
@ -176,7 +171,7 @@ struct AddProviderView: View {
extension AddProviderView { extension AddProviderView {
private func scrollToErrorMessage(_ proxy: ScrollViewProxy) { private func scrollToErrorMessage(_ proxy: ScrollViewProxy) {
proxy.maybeScrollTo(viewModel.providers.last?.id, animated: true) proxy.maybeScrollTo(providers.last?.id, animated: true)
} }
} }

View File

@ -34,11 +34,6 @@ extension AddProviderView {
case provider(ProviderName) case provider(ProviderName)
} }
// local copy for animations
@Published var providers: [ProviderMetadata] = []
@Published private(set) var newProviders: [ProviderMetadata] = []
@Published var selectedVPNProtocol: VPNProtocolType = .openVPN @Published var selectedVPNProtocol: VPNProtocolType = .openVPN
@Published var selectedProvider: ProviderMetadata? @Published var selectedProvider: ProviderMetadata?
@ -107,8 +102,6 @@ extension AddProviderView {
try await providerManager.fetchProvidersIndexPublisher( try await providerManager.fetchProvidersIndexPublisher(
priority: .remoteThenBundle priority: .remoteThenBundle
).async() ).async()
newProviders = providerManager.allProviders()
} catch { } catch {
errorMessage = error.localizedDescription errorMessage = error.localizedDescription
} }
@ -123,7 +116,7 @@ extension AddProviderView {
} }
extension AddProviderView.NameView { extension AddProviderView.NameView {
struct ViewModel { struct ViewModel: Equatable {
private var isNamePreset = false private var isNamePreset = false
var profileName = "" var profileName = ""