summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift
blob: d00eb5e49e769cdc0a32525e3f91407d2c7b26a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//
//  ConnectionView.swift
//  MullvadVPN
//
//  Created by Jon Petersson on 2024-12-03.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import SwiftUI

struct ConnectionView: View {
    @ObservedObject var connectionViewModel: ConnectionViewViewModel
    @ObservedObject var indicatorsViewModel: FeatureIndicatorsViewModel

    @State private(set) var isExpanded = false

    @State private(set) var scrollViewHeight: CGFloat = 0
    var hasFeatureIndicators: Bool { !indicatorsViewModel.chips.isEmpty }
    var action: ButtonPanel.Action?

    var body: some View {
        VStack {
            Spacer()
                .accessibilityIdentifier(AccessibilityIdentifier.connectionView.asString)
            VStack(spacing: 16) {
                VStack(alignment: .leading, spacing: 0) {
                    HeaderView(viewModel: connectionViewModel, isExpanded: $isExpanded)
                        .padding(.bottom, 4)

                    Divider()
                        .background(UIColor.secondaryTextColor.color)
                        .padding(.top, 4)
                        .padding(.bottom, 8)
                        .showIf(isExpanded)

                    ScrollView {
                        VStack(alignment: .leading, spacing: 2) {
                            if let titleForCountryAndCity = connectionViewModel.titleForCountryAndCity {
                                Text(titleForCountryAndCity)
                                    .lineLimit(isExpanded ? 2 : 1)
                                    .font(.title3.weight(.semibold))
                                    .foregroundStyle(UIColor.primaryTextColor.color)
                            }
                            if let titleForServer = connectionViewModel.titleForServer {
                                Text(titleForServer)
                                    .lineLimit(isExpanded ? 3 : 1)
                                    .font(.body)
                                    .foregroundStyle(UIColor.primaryTextColor.color.opacity(0.6))
                                    .accessibilityIdentifier(
                                        AccessibilityIdentifier.connectionPanelServerLabel.asString
                                    )
                                    .multilineTextAlignment(.leading)
                                    .fixedSize(horizontal: false, vertical: true)
                            }
                            HStack {
                                VStack(alignment: .leading, spacing: 0) {
                                    Text(LocalizedStringKey("Active features"))
                                        .font(.footnote.weight(.semibold))
                                        .foregroundStyle(UIColor.primaryTextColor.color.opacity(0.6))
                                        .padding(.top, 8)
                                        .showIf(isExpanded && hasFeatureIndicators)

                                    ChipContainerView(
                                        viewModel: indicatorsViewModel,
                                        tunnelState: connectionViewModel.tunnelStatus.state,
                                        isExpanded: $isExpanded
                                    )
                                    .padding(.bottom, isExpanded ? 16 : 0)
                                    .showIf(hasFeatureIndicators)

                                    DetailsView(viewModel: connectionViewModel)
                                        .padding(.bottom, 8)
                                        .padding(.top, !hasFeatureIndicators ? 8 : 0)
                                        .showIf(isExpanded)
                                }
                                Spacer()
                            }
                        }.frame(maxWidth: .infinity, alignment: .leading)
                            .sizeOfView { size in
                                withAnimation {
                                    scrollViewHeight = size.height
                                }
                            }
                    }
                    .frame(maxHeight: scrollViewHeight)
                    .apply {
                        if #available(iOS 16.4, *) {
                            $0.scrollBounceBehavior(.basedOnSize)
                        } else {
                            $0
                        }
                    }
                }
                .transformEffect(.identity)
                .animation(.default, value: hasFeatureIndicators)
                ButtonPanel(viewModel: connectionViewModel, action: action)
            }
            .padding(16)
            .background(BlurView(style: .dark))
            .cornerRadius(12)
            .padding(EdgeInsets(top: 16, leading: 16, bottom: 24, trailing: 16))
            .onChange(of: connectionViewModel.showsConnectionDetails) { showsConnectionDetails in
                if !showsConnectionDetails {
                    withAnimation {
                        isExpanded = false
                    }
                }
            }
        }
    }
}

#Preview("ConnectionView (Indicators)") {
    ConnectionViewComponentPreview(showIndicators: true) { indicatorModel, viewModel, _ in
        ConnectionView(connectionViewModel: viewModel, indicatorsViewModel: indicatorModel)
    }
}

#Preview("ConnectionView (No indicators)") {
    ConnectionViewComponentPreview(showIndicators: false) { indicatorModel, viewModel, _ in
        ConnectionView(connectionViewModel: viewModel, indicatorsViewModel: indicatorModel)
    }
}