Skip to content

Commit cdc8596

Browse files
committed
refactor: improve UI and code quality
- Redesign status bar details view with header, traffic summary, colored icons - Use monospaced font for stable number display - Add separator before quit menu item - Add empty state placeholder for no network activity - Fix Swift naming conventions (Total→total, Items→items) - Add Identifiable conformance to NetworkState - Add stop() cleanup on app termination - Replace WindowGroup with Settings to prevent empty window - Add localization for new UI strings - Compact status bar width (62pt)
1 parent 063561d commit cdc8596

6 files changed

Lines changed: 113 additions & 36 deletions

File tree

NetworkStatusBar/NetworkStatus.swift

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ import Foundation
99
import TabularData
1010

1111
struct NetworkStates {
12-
var Total: NetworkState = NetworkState()
13-
var Items: [NetworkState] = []
12+
var total: NetworkState = NetworkState()
13+
var items: [NetworkState] = []
1414
}
1515

16-
struct NetworkState {
16+
struct NetworkState: Identifiable {
17+
var id: String { "\(pid).\(name)" }
1718
var pid: Int = 0
1819
var name: String = ""
1920
var inbounds: Int = 0
@@ -64,6 +65,12 @@ open class NetworkDetails {
6465
process.waitUntilExit()
6566
}
6667

68+
func stop() {
69+
if process.isRunning {
70+
process.terminate()
71+
}
72+
}
73+
6774
func update(data: Data) {
6875
if data.isEmpty {
6976
return
@@ -113,8 +120,8 @@ open class NetworkDetails {
113120

114121
items.sort { $0.total > $1.total }
115122
let ret = NetworkStates(
116-
Total: NetworkState(inbounds: totalin, outbounds: totalout),
117-
Items: items)
123+
total: NetworkState(inbounds: totalin, outbounds: totalout),
124+
items: items)
118125

119126
self.callback(ret)
120127
}

NetworkStatusBar/NetworkStatusBarApp.swift

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import SwiftUI
1111
struct NetworkStatusBarApp: App {
1212
@NSApplicationDelegateAdaptor(AppDelegate.self) var appdelegate
1313
var body: some Scene {
14-
WindowGroup {}
14+
Settings {
15+
EmptyView()
16+
}
1517
}
1618
}
1719

18-
var StatusBarWidth = CGFloat(49)
19-
2020
class AppDelegate: NSObject, NSApplicationDelegate {
2121

2222
var iostates: IOStates = IOStates()
@@ -26,8 +26,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
2626

2727
func onUpdate(update: NetworkStates) {
2828
DispatchQueue.main.async {
29-
self.iostates.total = update.Total
30-
self.iostates.items = update.Items.filter({ item in
29+
self.iostates.total = update.total
30+
self.iostates.items = update.items.filter({ item in
3131
return item.total > 1024
3232
})
3333
}
@@ -38,8 +38,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
3838
}
3939

4040
func applicationDidFinishLaunching(_ notification: Notification) {
41-
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
42-
statusItem?.length = StatusBarWidth
41+
statusItem = NSStatusBar.system.statusItem(withLength: 62)
4342

4443
networkStatus.callback = onUpdate
4544
DispatchQueue.global(qos: .userInteractive).async {
@@ -48,7 +47,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
4847

4948
if let button = statusItem?.button {
5049
let statusbarview = NSHostingView(rootView: StatusBarView(iostates: iostates))
51-
statusbarview.frame = button.frame
50+
statusbarview.frame = NSRect(x: 0, y: 0, width: 62, height: button.frame.height)
5251
button.addSubview(statusbarview)
5352
}
5453

@@ -57,13 +56,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
5756
menu.items = [
5857
{
5958
let menuitem = NSMenuItem()
60-
menuitem.view = NSHostingView(rootView: StatusBarDetailsView(iostates: iostates))
61-
// ListTableCellView has 16dp at left and right,ListScroll has a 15dp width
62-
// 16 * 2 + 15 = 47
63-
// Out DetailsItem has min length 200
64-
menuitem.view?.setFrameSize(NSSize(width: 247, height: 100))
59+
let detailsView = StatusBarDetailsView(iostates: iostates)
60+
let hostingView = NSHostingView(rootView: detailsView)
61+
hostingView.setFrameSize(NSSize(width: 280, height: 320))
62+
menuitem.view = hostingView
6563
return menuitem
6664
}(),
65+
NSMenuItem.separator(),
6766
NSMenuItem(
6867
title: NSLocalizedString("quit", comment: "quit the application"),
6968
action: #selector(quit),
@@ -74,6 +73,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
7473
}()
7574
}
7675

76+
func applicationWillTerminate(_ notification: Notification) {
77+
networkStatus.stop()
78+
}
79+
7780
@IBAction func quit(obj: Any) {
7881
NSApp.terminate(nil)
7982
}

NetworkStatusBar/StatusBarDetailsView.swift

Lines changed: 78 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,53 +6,116 @@
66
//
77

88
import SwiftUI
9-
import TabularData
109

1110
struct StatusBarDetailsView: View {
1211
@ObservedObject var iostates = IOStates()
1312
var body: some View {
14-
List(iostates.items, id: \.pid) { item in
15-
StatusBarDetailsItemView(state: item)
13+
VStack(alignment: .leading, spacing: 0) {
14+
// Header: Total traffic summary
15+
VStack(alignment: .leading, spacing: 6) {
16+
Text(NSLocalizedString("network_traffic", comment: "Network Traffic"))
17+
.font(.system(size: 13, weight: .semibold))
18+
HStack(spacing: 16) {
19+
Label {
20+
Text(formatbytes(iostates.total.outbounds))
21+
.font(.system(size: 11, weight: .medium, design: .monospaced))
22+
} icon: {
23+
Image(systemName: "arrow.up")
24+
.font(.system(size: 9, weight: .semibold))
25+
.foregroundColor(.orange)
26+
}
27+
Label {
28+
Text(formatbytes(iostates.total.inbounds))
29+
.font(.system(size: 11, weight: .medium, design: .monospaced))
30+
} icon: {
31+
Image(systemName: "arrow.down")
32+
.font(.system(size: 9, weight: .semibold))
33+
.foregroundColor(.cyan)
34+
}
35+
}
36+
}
37+
.padding(.horizontal, 12)
38+
.padding(.vertical, 10)
39+
40+
Divider()
41+
.padding(.horizontal, 8)
42+
43+
// Process list
44+
if iostates.items.isEmpty {
45+
Text(NSLocalizedString("no_activity", comment: "No network activity"))
46+
.foregroundColor(.secondary)
47+
.font(.system(size: 11))
48+
.frame(maxWidth: .infinity, alignment: .center)
49+
.padding(.vertical, 20)
50+
} else {
51+
ScrollView {
52+
VStack(spacing: 0) {
53+
ForEach(iostates.items) { item in
54+
StatusBarDetailsItemView(state: item)
55+
}
56+
}
57+
}
58+
.frame(maxHeight: 260)
59+
}
1660
}
17-
.listStyle(.sidebar)
61+
.frame(width: 280)
1862
}
1963
}
2064

2165
struct StatusBarDetailsItemView: View {
2266
var state: NetworkState = NetworkState()
2367

2468
var body: some View {
25-
HStack(alignment: VerticalAlignment.center, spacing: 0) {
69+
HStack {
2670
Text(state.name)
27-
.frame(width: 140, alignment: Alignment.leading)
28-
VStack(alignment: .trailing) {
29-
Text(String(format: "%@ ▲", arguments: [formatbytes(state.outbounds)]))
30-
Text(String(format: "%@ ▼", arguments: [formatbytes(state.inbounds)]))
71+
.font(.system(size: 11))
72+
.lineLimit(1)
73+
.truncationMode(.tail)
74+
.frame(maxWidth: .infinity, alignment: .leading)
75+
.help(state.name)
76+
77+
VStack(alignment: .trailing, spacing: 1) {
78+
HStack(spacing: 2) {
79+
Image(systemName: "arrow.up")
80+
.font(.system(size: 7, weight: .medium))
81+
.foregroundColor(.orange)
82+
Text(formatbytes(state.outbounds))
83+
.font(.system(size: 9, weight: .medium, design: .monospaced))
84+
}
85+
HStack(spacing: 2) {
86+
Image(systemName: "arrow.down")
87+
.font(.system(size: 7, weight: .medium))
88+
.foregroundColor(.cyan)
89+
Text(formatbytes(state.inbounds))
90+
.font(.system(size: 9, weight: .medium, design: .monospaced))
91+
}
3192
}
3293
.fixedSize()
33-
.font(.system(size: 9, weight: .medium))
34-
.frame(width: 60, alignment: Alignment.trailing)
3594
}
95+
.padding(.horizontal, 12)
96+
.padding(.vertical, 5)
3697
}
3798
}
3899

39100
struct StatusBarMenuView_Previews: PreviewProvider {
40101
static let data = { () -> IOStates in
41102
let ret = IOStates()
103+
ret.total = NetworkState(pid: 0, name: "total", inbounds: 10240, outbounds: 2048)
42104
ret.items = [
43-
NetworkState(pid: 1, name: "process", inbounds: 512, outbounds: 512),
44-
NetworkState(pid: 2, name: "process", inbounds: 64, outbounds: 1),
105+
NetworkState(pid: 1, name: "Chrome", inbounds: 5120, outbounds: 1024),
106+
NetworkState(pid: 2, name: "Slack", inbounds: 2048, outbounds: 512),
107+
NetworkState(pid: 3, name: "Terminal", inbounds: 1024, outbounds: 256),
45108
]
46109
return ret
47110
}()
48111
static var previews: some View {
49112
StatusBarDetailsView(iostates: data)
50-
.frame(width: 200.0)
51113
}
52114
}
53115

54116
struct StatusBarDetailsItemView_Previews: PreviewProvider {
55117
static var previews: some View {
56-
StatusBarDetailsItemView(state: NetworkState(name: "process", inbounds: 1023, outbounds: 1000))
118+
StatusBarDetailsItemView(state: NetworkState(pid: 1, name: "Chrome", inbounds: 10240, outbounds: 2048))
119+
.frame(width: 280)
57120
}
58121
}

NetworkStatusBar/StatusBarView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ class IOStates: ObservableObject {
1515
struct StatusBarView: View {
1616
@ObservedObject var iostates = IOStates()
1717
var body: some View {
18-
VStack(alignment: .trailing) {
18+
VStack(alignment: .trailing, spacing: 2) {
1919
Text(String(format: "%@ ▲", arguments: [formatbytes(iostates.total.outbounds)]))
2020
Text(String(format: "%@ ▼", arguments: [formatbytes(iostates.total.inbounds)]))
2121
}
22-
.font(.system(size: 9, weight: .medium))
22+
.font(.system(size: 9, weight: .medium, design: .monospaced))
2323
}
2424
}
2525

NetworkStatusBar/en.lproj/Localizable.strings

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@
55
Created by fatal cn on 2021/10/31.
66

77
*/
8-
"quit"="quit";
8+
"quit"="Quit";
9+
"network_traffic"="Network Traffic";
10+
"no_activity"="No network activity";

NetworkStatusBar/zh-Hans.lproj/Localizable.strings

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@
66

77
*/
88
"quit"="退出";
9+
"network_traffic"="网络流量";
10+
"no_activity"="暂无网络活动";

0 commit comments

Comments
 (0)