Skip to content

Commit c9a4dfb

Browse files
authored
feat(ui): add app status icon (#156)
1 parent b1e132b commit c9a4dfb

20 files changed

+593
-377
lines changed

Bitkit/AppScene.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ struct AppScene: View {
77

88
@StateObject private var app: AppViewModel
99
@StateObject private var navigation = NavigationViewModel()
10+
@StateObject private var network = NetworkMonitor()
1011
@StateObject private var sheets = SheetViewModel()
1112
@StateObject private var wallet = WalletViewModel()
1213
@StateObject private var currency = CurrencyViewModel()
@@ -64,6 +65,7 @@ struct AppScene: View {
6465
.onChange(of: scenePhase, perform: handleScenePhaseChange)
6566
.environmentObject(app)
6667
.environmentObject(navigation)
68+
.environmentObject(network)
6769
.environmentObject(sheets)
6870
.environmentObject(wallet)
6971
.environmentObject(currency)

Bitkit/Components/AppStatus.swift

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import SwiftUI
2+
3+
struct AppStatus: View {
4+
@EnvironmentObject private var app: AppViewModel
5+
@EnvironmentObject private var network: NetworkMonitor
6+
@EnvironmentObject private var wallet: WalletViewModel
7+
8+
let showText: Bool
9+
let showReady: Bool
10+
let showColor: Bool
11+
let testID: String
12+
let onPress: (() -> Void)?
13+
14+
@State private var rotationAngle: Double = 0
15+
@State private var opacity: Double = 1
16+
17+
init(
18+
showText: Bool = false,
19+
showReady: Bool = false,
20+
showColor: Bool = true,
21+
testID: String,
22+
onPress: (() -> Void)? = nil
23+
) {
24+
self.showText = showText
25+
self.showReady = showReady
26+
self.showColor = showColor
27+
self.testID = testID
28+
self.onPress = onPress
29+
}
30+
31+
var body: some View {
32+
Button(action: {
33+
onPress?()
34+
}) {
35+
HStack(spacing: 8) {
36+
statusIcon
37+
38+
if showText {
39+
BodyMSBText(t("wallet__drawer__status"), textColor: .black)
40+
}
41+
}
42+
}
43+
.frame(minWidth: 32, minHeight: 32)
44+
.accessibilityIdentifier(testID)
45+
.buttonStyle(PlainButtonStyle())
46+
.onAppear {
47+
startAnimations()
48+
}
49+
.onChange(of: appStatus) { _ in
50+
startAnimations()
51+
}
52+
}
53+
54+
// MARK: - Computed Properties
55+
56+
private var appStatus: HealthStatus {
57+
// During initialization, return 'ready' instead of error
58+
if !app.appStatusInitialized {
59+
return .ready
60+
}
61+
62+
return AppStatusHelper.combinedAppStatus(from: wallet, network: network)
63+
}
64+
65+
private var statusColor: Color {
66+
if !showColor {
67+
return .black
68+
}
69+
70+
return appStatus.iconColor
71+
}
72+
73+
@ViewBuilder
74+
private var statusIcon: some View {
75+
switch appStatus {
76+
case .ready:
77+
if showReady {
78+
Image("power")
79+
.resizable()
80+
.foregroundColor(statusColor)
81+
.frame(width: 24, height: 24)
82+
}
83+
case .pending:
84+
Image("arrows-clockwise")
85+
.resizable()
86+
.foregroundColor(statusColor)
87+
.frame(width: 24, height: 24)
88+
.rotationEffect(.degrees(rotationAngle))
89+
case .error:
90+
Image("warning")
91+
.resizable()
92+
.foregroundColor(statusColor)
93+
.frame(width: 24, height: 24)
94+
.opacity(opacity)
95+
}
96+
}
97+
98+
// MARK: - Animations
99+
100+
private func startAnimations() {
101+
switch appStatus {
102+
case .ready:
103+
stopAnimations()
104+
case .pending:
105+
startRotationAnimation()
106+
case .error:
107+
startFadeAnimation()
108+
}
109+
}
110+
111+
private func startRotationAnimation() {
112+
// Reset rotation to 0 first
113+
rotationAngle = 0
114+
115+
// First half turn with easing (0 to 180 degrees)
116+
withAnimation(.timingCurve(0.4, 0, 0.2, 1, duration: 0.8)) {
117+
rotationAngle = 180
118+
}
119+
120+
// Second half turn with different timing (180 to 360 degrees)
121+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
122+
withAnimation(.timingCurve(0.4, 0, 0.2, 1, duration: 1.2)) {
123+
rotationAngle = 360
124+
}
125+
126+
// Reset and repeat the sequence
127+
DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) {
128+
rotationAngle = 0
129+
startRotationAnimation()
130+
}
131+
}
132+
}
133+
134+
private func startFadeAnimation() {
135+
withAnimation(
136+
.easeInOut(duration: 0.6)
137+
.repeatForever(autoreverses: true)
138+
) {
139+
opacity = opacity == 1 ? 0.3 : 1
140+
}
141+
}
142+
143+
private func stopAnimations() {
144+
withAnimation(.easeInOut(duration: 0.3)) {
145+
rotationAngle = 0
146+
opacity = 1
147+
}
148+
}
149+
}

Bitkit/Components/Button/Button.swift

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,6 @@ struct CustomButton: View {
4949
case .large: return 56
5050
}
5151
}
52-
53-
var horizontalPadding: CGFloat {
54-
switch self {
55-
case .small: return 12
56-
case .large: return 16
57-
}
58-
}
5952
}
6053

6154
enum Variant {

Bitkit/Components/Button/PrimaryButtonView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ struct PrimaryButtonView: View {
2929
}
3030
.frame(maxWidth: (size == .large || shouldExpand) ? .infinity : nil)
3131
.frame(height: size.height)
32-
.padding(.horizontal, size.horizontalPadding)
32+
.padding(.horizontal, 16)
3333
.background(backgroundGradient)
3434
.cornerRadius(64)
3535
.shadow(color: shadowColor, radius: 0, x: 0, y: -1)

Bitkit/Components/Button/TertiaryButtonView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ struct TertiaryButtonView: View {
1515
}
1616
.frame(maxWidth: .infinity)
1717
.frame(height: CustomButton.Size.large.height)
18-
.padding(.horizontal, CustomButton.Size.large.horizontalPadding)
18+
.padding(.horizontal, 16)
1919
.background(.clear)
2020
.contentShape(Rectangle())
2121
}

Bitkit/Components/DrawerView.swift

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import SwiftUI
22

3-
// TODO: maybe move to a separate file
43
enum DrawerMenuItem: Int, CaseIterable, Identifiable, Hashable {
54
case wallet
65
case activity
@@ -100,9 +99,18 @@ struct DrawerView: View {
10099

101100
Spacer()
102101

103-
appStatus()
104-
.padding(.horizontal, 24)
105-
.padding(.bottom, 16)
102+
AppStatus(
103+
showText: true,
104+
showReady: true,
105+
showColor: false,
106+
testID: "DrawerAppStatus",
107+
onPress: {
108+
navigation.activeDrawerMenuItem = .appStatus
109+
closeMenu()
110+
}
111+
)
112+
.padding(.horizontal, 24)
113+
.padding(.bottom, 16)
106114
}
107115
.frame(width: geometry.size.width * 0.5, height: geometry.size.height)
108116
.background(Color.brandAccent)
@@ -169,22 +177,6 @@ struct DrawerView: View {
169177
}
170178
.padding(.horizontal, 16)
171179
}
172-
173-
@ViewBuilder
174-
private func appStatus() -> some View {
175-
Button {
176-
navigation.activeDrawerMenuItem = .appStatus
177-
closeMenu()
178-
} label: {
179-
HStack(spacing: 8) {
180-
Image(wallet.nodeLifecycleState.statusIcon)
181-
.resizable()
182-
.foregroundColor(.black)
183-
.frame(width: 24, height: 24)
184-
BodyMSBText(DrawerMenuItem.appStatus.label, textColor: .black)
185-
}
186-
}
187-
}
188180
}
189181

190182
#Preview {

Bitkit/Components/Header.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import SwiftUI
2+
3+
struct Header: View {
4+
@EnvironmentObject var app: AppViewModel
5+
@EnvironmentObject var navigation: NavigationViewModel
6+
7+
var body: some View {
8+
HStack(alignment: .center, spacing: 0) {
9+
Button {
10+
if app.hasSeenProfileIntro {
11+
navigation.navigate(.profile)
12+
} else {
13+
navigation.navigate(.profileIntro)
14+
}
15+
} label: {
16+
HStack(alignment: .center, spacing: 16) {
17+
Image(systemName: "person.circle.fill")
18+
.resizable()
19+
.font(.title2)
20+
.foregroundColor(.gray1)
21+
.frame(width: 32, height: 32)
22+
23+
TitleText(t("slashtags__your_name_capital"))
24+
}
25+
}
26+
27+
Spacer()
28+
29+
HStack(alignment: .center, spacing: 12) {
30+
AppStatus(
31+
testID: "HeaderAppStatus",
32+
onPress: {
33+
navigation.navigate(.appStatus)
34+
}
35+
)
36+
37+
Button {
38+
withAnimation {
39+
app.showDrawer = true
40+
}
41+
} label: {
42+
Image("burger")
43+
.resizable()
44+
.foregroundColor(.textPrimary)
45+
.frame(width: 24, height: 24)
46+
}
47+
.frame(width: 32, height: 32)
48+
}
49+
}
50+
.frame(height: 48)
51+
.zIndex(.infinity)
52+
.padding(.leading, 16)
53+
.padding(.trailing, 10)
54+
}
55+
}

Bitkit/Components/NavigationBar.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,17 @@ struct NavigationBar: View {
7575
}
7676
} label: {
7777
Image("burger")
78+
.resizable()
79+
.foregroundColor(.textPrimary)
80+
.frame(width: 24, height: 24)
7881
}
82+
.frame(width: 32, height: 32)
83+
.offset(x: 6)
7984
} else {
8085
Spacer()
8186
.frame(width: 24, height: 24)
8287
}
8388
}
8489
.frame(height: 48)
85-
.offset(y: -5)
8690
}
8791
}

Bitkit/Components/StatusRow.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import SwiftUI
2+
3+
struct StatusRow: View {
4+
let imageName: String
5+
let title: String
6+
let description: String
7+
let status: HealthStatus
8+
let onTap: (() -> Void)?
9+
10+
init(
11+
imageName: String,
12+
title: String,
13+
description: String,
14+
status: HealthStatus,
15+
onTap: (() -> Void)? = nil
16+
) {
17+
self.imageName = imageName
18+
self.title = title
19+
self.description = description
20+
self.status = status
21+
self.onTap = onTap
22+
}
23+
24+
var body: some View {
25+
VStack(spacing: 0) {
26+
HStack(spacing: 16) {
27+
CircularIcon(
28+
icon: imageName,
29+
iconColor: status.iconColor,
30+
backgroundColor: status.iconBackground,
31+
size: 32
32+
)
33+
34+
VStack(alignment: .leading, spacing: 0) {
35+
BodyMSBText(title)
36+
CaptionBText(description)
37+
}
38+
39+
Spacer()
40+
}
41+
42+
Divider()
43+
.padding(.top, 16)
44+
}
45+
.contentShape(Rectangle())
46+
.onTapGesture {
47+
onTap?()
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)