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 {
completeView
}
}
}.animation(.default, value: viewModel)
// hidden
NavigationLink("", isActive: $isEnteringCredentials) {
@ -159,31 +159,23 @@ struct AddHostView: View {
title: Text(L10n.AddProfile.Shared.title),
message: Text(L10n.AddProfile.Shared.Alerts.Overwrite.message),
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))
)
}
private func processProfile(replacingExisting: Bool) {
withAnimation {
viewModel.processURL(
url,
with: profileManager,
replacingExisting: replacingExisting,
deletingURLOnSuccess: deletingURLOnSuccess
)
}
viewModel.processURL(
url,
with: profileManager,
replacingExisting: replacingExisting,
deletingURLOnSuccess: deletingURLOnSuccess
)
}
private func saveProfile() {
let result = withAnimation {
viewModel.addProcessedProfile(to: profileManager)
}
let result = viewModel.addProcessedProfile(to: profileManager)
guard result else {
return
}

View File

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

View File

@ -96,24 +96,18 @@ extension AddProviderView {
title: Text(L10n.AddProfile.Shared.title),
message: Text(L10n.AddProfile.Shared.Alerts.Overwrite.message),
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))
)
}
private func saveProfile(replacingExisting: Bool) {
let addedProfile = withAnimation {
viewModel.addProfile(
profile,
to: profileManager,
replacingExisting: replacingExisting
)
}
let addedProfile = viewModel.addProfile(
profile,
to: profileManager,
replacingExisting: replacingExisting
)
guard let addedProfile = addedProfile else {
return
}

View File

@ -41,9 +41,16 @@ struct AddProviderView: View {
self.bindings = bindings
}
private var providers: [ProviderMetadata] {
providerManager.allProviders()
.filter {
$0.supportedVPNProtocols.contains(viewModel.selectedVPNProtocol)
}.sorted()
}
private var availableVPNProtocols: [VPNProtocolType] {
var protos: Set<VPNProtocolType> = []
viewModel.providers.forEach {
providers.forEach {
$0.supportedVPNProtocols.forEach {
protos.insert($0)
}
@ -70,16 +77,17 @@ struct AddProviderView: View {
ScrollViewReader { scrollProxy in
List {
mainSection
if !viewModel.providers.isEmpty {
if !providers.isEmpty {
providersSection
}
}.onChange(of: viewModel.errorMessage) {
onErrorMessage($0, scrollProxy)
}.disabled(viewModel.pendingOperation != nil)
.animation(.default, value: providers)
}
// hidden
ForEach(viewModel.providers, id: \.navigationId, content: providerNavigationLink)
ForEach(providers, id: \.navigationId, content: providerNavigationLink)
}.themeSecondaryView()
.navigationTitle(L10n.AddProfile.Shared.title)
.toolbar(content: toolbar)
@ -87,12 +95,6 @@ struct AddProviderView: View {
NavigationView {
PaywallView(isPresented: $viewModel.isPaywallPresented)
}.themeGlobal()
}.onAppear {
refreshProviders()
}.onChange(of: viewModel.newProviders) { newValue in
withAnimation {
refreshProviders(newValue)
}
}
}
@ -122,7 +124,7 @@ struct AddProviderView: View {
Section(
footer: themeErrorMessage(viewModel.errorMessage)
) {
ForEach(viewModel.providers, content: providerRow)
ForEach(providers, content: providerRow)
}
}
@ -150,13 +152,6 @@ struct AddProviderView: View {
}.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
private func presentOrPurchaseProvider(_ metadata: ProviderMetadata) {
if productManager.isEligible(forProvider: metadata.name) {
@ -176,7 +171,7 @@ struct AddProviderView: View {
extension AddProviderView {
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)
}
// local copy for animations
@Published var providers: [ProviderMetadata] = []
@Published private(set) var newProviders: [ProviderMetadata] = []
@Published var selectedVPNProtocol: VPNProtocolType = .openVPN
@Published var selectedProvider: ProviderMetadata?
@ -107,8 +102,6 @@ extension AddProviderView {
try await providerManager.fetchProvidersIndexPublisher(
priority: .remoteThenBundle
).async()
newProviders = providerManager.allProviders()
} catch {
errorMessage = error.localizedDescription
}
@ -123,7 +116,7 @@ extension AddProviderView {
}
extension AddProviderView.NameView {
struct ViewModel {
struct ViewModel: Equatable {
private var isNamePreset = false
var profileName = ""