Skip to content

Commit

Permalink
Fix multiple small issues in ConnectionView
Browse files Browse the repository at this point in the history
  • Loading branch information
rablador committed Jan 9, 2025
1 parent 712d19e commit 20ddffe
Show file tree
Hide file tree
Showing 15 changed files with 123 additions and 87 deletions.
4 changes: 4 additions & 0 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@
7AA1309F2D007B2500640DF9 /* VisualEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA1309E2D007B2500640DF9 /* VisualEffectView.swift */; };
7AA130A12D01B1E200640DF9 /* SplitMainButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA130A02D01B1E200640DF9 /* SplitMainButton.swift */; };
7AA513862BC91C6B00D081A4 /* LogRotationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA513852BC91C6B00D081A4 /* LogRotationTests.swift */; };
7AA636382D2D3BB0009B2C89 /* View+Conditionals.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA636372D2D3BAC009B2C89 /* View+Conditionals.swift */; };
7AA7046A2C8EFE2B0045699D /* StoredRelays.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA704682C8EFE050045699D /* StoredRelays.swift */; };
7AB2B6702BA1EB8C00B03E3B /* ListCustomListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB2B66E2BA1EB8C00B03E3B /* ListCustomListViewController.swift */; };
7AB2B6712BA1EB8C00B03E3B /* ListCustomListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB2B66F2BA1EB8C00B03E3B /* ListCustomListCoordinator.swift */; };
Expand Down Expand Up @@ -2002,6 +2003,7 @@
7AA1309E2D007B2500640DF9 /* VisualEffectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectView.swift; sourceTree = "<group>"; };
7AA130A02D01B1E200640DF9 /* SplitMainButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitMainButton.swift; sourceTree = "<group>"; };
7AA513852BC91C6B00D081A4 /* LogRotationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogRotationTests.swift; sourceTree = "<group>"; };
7AA636372D2D3BAC009B2C89 /* View+Conditionals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Conditionals.swift"; sourceTree = "<group>"; };
7AA704682C8EFE050045699D /* StoredRelays.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredRelays.swift; sourceTree = "<group>"; };
7AB2B66E2BA1EB8C00B03E3B /* ListCustomListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListCustomListViewController.swift; sourceTree = "<group>"; };
7AB2B66F2BA1EB8C00B03E3B /* ListCustomListCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListCustomListCoordinator.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3173,6 +3175,7 @@
7A21DACE2A30AA3700A787A9 /* UITextField+Appearance.swift */,
5878F4FF29CDA742003D4BE2 /* UIView+AutoLayoutBuilder.swift */,
7A516C2D2B6D357500BBD33D /* URL+Scoping.swift */,
7AA636372D2D3BAC009B2C89 /* View+Conditionals.swift */,
7A0EAE9D2D01BCBF00D3EB8B /* View+Size.swift */,
7A8A18FA2CE4B66C000BCB5B /* View+TapAreaSize.swift */,
);
Expand Down Expand Up @@ -5946,6 +5949,7 @@
F01DAE332C2B032A00521E46 /* RelaySelection.swift in Sources */,
58B993B12608A34500BA7811 /* LoginContentView.swift in Sources */,
7A516C2E2B6D357500BBD33D /* URL+Scoping.swift in Sources */,
7AA636382D2D3BB0009B2C89 /* View+Conditionals.swift in Sources */,
5878A27529093A310096FC88 /* StorePaymentEvent.swift in Sources */,
7A7AD28D29DC677800480EF1 /* FirstTimeLaunch.swift in Sources */,
F062000C2CB7EB5D002E6DB9 /* UIImage+Helpers.swift in Sources */,
Expand Down
41 changes: 41 additions & 0 deletions ios/MullvadVPN/Extensions/View+Conditionals.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// View+Conditionals.swift
// MullvadVPN
//
// Created by Jon Petersson on 2025-01-07.
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import SwiftUI

extension View {
@ViewBuilder func `if`<Content: View>(
_ conditional: Bool,
@ViewBuilder _ content: (Self) -> Content
) -> some View {
if conditional {
content(self)
} else {
self
}
}

@ViewBuilder func ifLet<Content: View, T>(
_ conditional: T?,
@ViewBuilder _ content: (Self, _ value: T) -> Content
) -> some View {
if let value = conditional {
content(self, value)
} else {
self
}
}

@ViewBuilder func showIf(_ conditional: Bool) -> some View {
if conditional {
self
} else {
EmptyView()
}
}
}
1 change: 0 additions & 1 deletion ios/MullvadVPN/Extensions/View+TapAreaSize.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,5 @@ private struct TappablePadding: ViewModifier {
height: max(actualViewSize.height, tappableViewSize.height)
)
.contentShape(Rectangle())
.frame(width: actualViewSize.width, height: actualViewSize.height)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,14 @@ struct ChipContainerView<ViewModel>: View where ViewModel: ChipViewModelProtocol
createChipViews(chips: chipsToAdd, containerWidth: containerWidth)
}

if showMoreButton {
Text(LocalizedStringKey("\(viewModel.chips.count - chipsToAdd.count) more..."))
.font(.subheadline)
.lineLimit(1)
.foregroundStyle(UIColor.primaryTextColor.color)
.onTapGesture {
isExpanded.toggle()
}
}
Text(LocalizedStringKey("\(viewModel.chips.count - chipsToAdd.count) more..."))
.font(.subheadline)
.lineLimit(1)
.foregroundStyle(UIColor.primaryTextColor.color)
.onTapGesture {
isExpanded.toggle()
}
.showIf(showMoreButton)

Spacer()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
import MullvadSettings
import SwiftUI

// Opting to use NSLocalizedString instead of LocalizedStringKey here in order
// to be able to fetch the string value at a later point (eg. in ChipViewModelProtocol,
// when calculating the text widths of the chips).

protocol ChipFeature {
var isEnabled: Bool { get }
var name: String { get }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SwiftUI
struct ChipView: View {
let item: ChipModel
var body: some View {
Text(LocalizedStringKey(item.name))
Text(item.name)
.font(.subheadline)
.lineLimit(1)
.foregroundStyle(UIColor.primaryTextColor.color)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,19 @@ protocol ChipViewModelProtocol: ObservableObject {
}

extension ChipViewModelProtocol {
func chipsToAdd(forContainerWidth containerWidth: CGFloat) -> (chips: [ChipModel], chipsWillOverflow: Bool) {
func chipsToAdd(forContainerWidth containerWidth: CGFloat) -> (chips: [ChipModel], isOverflowing: Bool) {
var chipsToAdd = [ChipModel]()
var chipsWillOverflow = false

let moreTextWidth = "\(chips.count) more..."
.width(using: .preferredFont(forTextStyle: .subheadline)) + 4 // Some extra to be safe.
var isOverflowing = false

let moreTextWidth = String(
format: NSLocalizedString(
"CONNECTION_VIEW_CHIPS_MORE",
tableName: "ConnectionView",
value: "@d more...",
comment: ""
), arguments: [chips.count]
)
.width(using: .preferredFont(forTextStyle: .subheadline)) + 4 // Some extra to be safe.
var totalChipsWidth: CGFloat = 0

for (index, chip) in chips.enumerated() {
Expand All @@ -33,20 +40,13 @@ extension ChipViewModelProtocol {
let chipWillFitWithMoreText = (totalChipsWidth + moreTextWidth) <= containerWidth
let chipWillFit = totalChipsWidth <= containerWidth

if chipWillFitWithMoreText {
// If a chip can fit together with the "more" text, add it.
chipsToAdd.append(chip)
chipsWillOverflow = !isLastChip
} else if chipWillFit && isLastChip {
// If a chip can fit and it's the last one, add it.
chipsToAdd.append(chip)
chipsWillOverflow = false
} else {
guard (chipWillFit && isLastChip) || chipWillFitWithMoreText else {
isOverflowing = true
break
}
}

return (chipsToAdd, chipsWillOverflow)
return (chipsToAdd, isOverflowing)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ extension ConnectionView {
text: viewModel.localizedTitleForSelectLocationButton,
image: .iconReload,
style: .default,
accessibilityId: .selectLocationButton,
primaryAction: { action?(.selectLocation) },
secondaryAction: { action?(.reconnect) }
)
.accessibilityIdentifier(AccessibilityIdentifier.selectLocationButton.asString)
case .disconnecting, .pendingReconnect, .disconnected:
MainButton(
text: viewModel.localizedTitleForSelectLocationButton,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,24 @@ struct ConnectionView: View {

var body: some View {
Spacer()
.accessibilityIdentifier(AccessibilityIdentifier.connectionView.asString)

VStack(spacing: 22) {
if connectionViewModel.showsActivityIndicator {
CustomProgressView(style: .large)
}
CustomProgressView(style: .large)
.showIf(connectionViewModel.showsActivityIndicator)

ZStack {
BlurView(style: .dark)

VStack(alignment: .leading, spacing: 16) {
HeaderView(viewModel: connectionViewModel, isExpanded: $isExpanded)

if connectionViewModel.showConnectionDetails {
DetailsContainer(
viewModel: connectionViewModel,
indicatorsViewModel: indicatorsViewModel,
isExpanded: $isExpanded
)
}
DetailsContainer(
connectionViewModel: connectionViewModel,
indicatorsViewModel: indicatorsViewModel,
isExpanded: $isExpanded
)
.showIf(connectionViewModel.showConnectionDetails)

ButtonPanel(viewModel: connectionViewModel, action: action)
}
Expand All @@ -45,19 +45,20 @@ struct ConnectionView: View {
.cornerRadius(12)
.padding(16)
}
.padding(.bottom, 8) // Adding some spacing so as not to overlap with the map legal link.
.accessibilityIdentifier(AccessibilityIdentifier.connectionView.asString)
.padding(.bottom, 8) // Some spacing to avoid overlap with the map legal link.
.onChange(of: isExpanded) { _ in
onContentUpdate?()
}
.onReceive(connectionViewModel.combinedState) { _, _ in
onContentUpdate?()

// Only update expanded state when connections details should be hidden.
// This will contract the view on eg. disconnect, but leave it as-is on
// eg. connect.
if !connectionViewModel.showConnectionDetails {
// withAnimation {
isExpanded = false
// }
return
}

onContentUpdate?()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ extension ConnectionViewViewModel {
var showConnectionDetails: Bool {
switch tunnelStatus.state {
case .connecting, .reconnecting, .waitingForConnectivity(.noConnection), .negotiatingEphemeralPeer,
.connected, .pendingReconnect, .waitingForConnectivity(.noNetwork):
.connected, .pendingReconnect:
true
case .disconnecting, .disconnected, .error:
case .disconnecting, .disconnected, .waitingForConnectivity(.noNetwork), .error:
false
}
}
Expand Down Expand Up @@ -130,9 +130,10 @@ extension ConnectionViewViewModel {

var localizedTitleForSelectLocationButton: LocalizedStringKey {
switch tunnelStatus.state {
case .disconnecting, .pendingReconnect, .disconnected:
case .disconnecting, .pendingReconnect, .disconnected, .waitingForConnectivity(.noNetwork):
LocalizedStringKey("Select location")
case .connecting, .connected, .reconnecting, .waitingForConnectivity, .negotiatingEphemeralPeer, .error:
case .connecting, .connected, .reconnecting, .waitingForConnectivity(.noConnection),
.negotiatingEphemeralPeer, .error:
LocalizedStringKey("Switch location")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,29 @@ import SwiftUI

extension ConnectionView {
internal struct DetailsContainer: View {
@StateObject var viewModel: ConnectionViewViewModel
@StateObject var connectionViewModel: ConnectionViewViewModel
@StateObject var indicatorsViewModel: FeatureIndicatorsViewModel
@Binding var isExpanded: Bool

@State private var scrollViewHeight: CGFloat = 0

var body: some View {
// if isExpanded {
Divider()
.background(UIColor.secondaryTextColor.color)
.opacity(isExpanded ? 1.0 : 0.0)
// }
.showIf(isExpanded)

// This geometry reader is somewhat of a workaround. It's "smart" in that it takes up as much
// space as it can and thereby helps the view to understand the maximum allowed height when
// placed in a UIKit context. If ConnectionView would ever be placed as a subview of SwiftUI
// parent, this reader could probably be removed.
GeometryReader { _ in
ScrollView {
VStack(spacing: 16) {
if !indicatorsViewModel.chips.isEmpty {
FeatureIndicatorsView(
viewModel: indicatorsViewModel,
isExpanded: $isExpanded
)
}
ScrollView {
VStack(spacing: 16) {
FeatureIndicatorsView(
viewModel: indicatorsViewModel,
isExpanded: $isExpanded
)
.showIf(!indicatorsViewModel.chips.isEmpty)

if isExpanded {
DetailsView(viewModel: viewModel)
.transition(.move(edge: .bottom))
}
}
.sizeOfView { scrollViewHeight = $0.height }
DetailsView(viewModel: connectionViewModel)
.showIf(isExpanded)
}
.sizeOfView { scrollViewHeight = $0.height }
}
.frame(maxHeight: scrollViewHeight)
}
Expand All @@ -53,7 +42,7 @@ extension ConnectionView {
#Preview {
ConnectionViewComponentPreview(showIndicators: true, isExpanded: true) { indicatorModel, viewModel, isExpanded in
ConnectionView.DetailsContainer(
viewModel: viewModel,
connectionViewModel: viewModel,
indicatorsViewModel: indicatorModel,
isExpanded: isExpanded
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,19 @@ extension ConnectionView {
}
.accessibilityLabel(viewModel.localizedAccessibilityLabelForSecureLabel)

if viewModel.showConnectionDetails {
Group {
Spacer()
Image(.iconChevron)
.renderingMode(.template)
.rotationEffect(isExpanded ? .degrees(-90) : .degrees(90))
.foregroundStyle(.white)
.transaction { transaction in
transaction.animation = nil
}
.accessibilityIdentifier(AccessibilityIdentifier.relayStatusCollapseButton.asString)
}
.showIf(viewModel.showConnectionDetails)
}
.accessibilityIdentifier(AccessibilityIdentifier.relayStatusCollapseButton.asString)
.contentShape(Rectangle())
.onTapGesture {
// withAnimation {
isExpanded.toggle()
// }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ struct FeatureIndicatorsView<ViewModel>: View where ViewModel: ChipViewModelProt

var body: some View {
VStack(alignment: .leading, spacing: 0) {
if isExpanded {
Text(LocalizedStringKey("Active features"))
.font(.footnote.weight(.semibold))
.foregroundStyle(UIColor.primaryTextColor.color.opacity(0.6))
.padding(.bottom, 8)
}
Text(LocalizedStringKey("Active features"))
.font(.footnote.weight(.semibold))
.foregroundStyle(UIColor.primaryTextColor.color.opacity(0.6))
.padding(.bottom, 8)
.showIf(isExpanded)

ChipContainerView(viewModel: viewModel, isExpanded: $isExpanded)
}
Expand Down
4 changes: 1 addition & 3 deletions ios/MullvadVPN/Views/MainButtonStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ import SwiftUI

struct MainButtonStyle: ButtonStyle {
var style: Style
var disabled: Bool
@Environment(\.isEnabled) private var isEnabled: Bool

init(_ style: Style, disabled: Bool = false) {
init(_ style: Style) {
self.style = style
self.disabled = disabled
}

func makeBody(configuration: Configuration) -> some View {
Expand Down
Loading

0 comments on commit 20ddffe

Please sign in to comment.