diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Views.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Views.swift index 8149d3b..571079c 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Views.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Views.swift @@ -17,13 +17,19 @@ public struct RootView: View { Group { switch store.authState { case .ready: - ChatListView(viewModel: chatListViewModel, bridge: store.bridge) + ChatListView( + viewModel: chatListViewModel, + bridge: store.bridge, + networkState: store.networkState, + typingState: store.typingState + ) default: AuthView(state: store.authState, viewModel: authViewModel) } } .task { await store.refreshAuthState() + await store.refreshNetworkState() } } } @@ -118,22 +124,34 @@ public struct AuthView: View { public struct ChatListView: View { @ObservedObject public var viewModel: ChatListViewModel public let bridge: SessionBridge + public var networkState: NetworkState + public var typingState: TypingState @State private var selectedChat: ChatSummary? @State private var showsAccountSwitcher = false - public init(viewModel: ChatListViewModel, bridge: SessionBridge) { + public init( + viewModel: ChatListViewModel, + bridge: SessionBridge, + networkState: NetworkState = .ready, + typingState: TypingState = .idle + ) { self.viewModel = viewModel self.bridge = bridge + self.networkState = networkState + self.typingState = typingState } public var body: some View { NavigationSplitView { - List(selection: $selectedChat) { - ForEach(viewModel.filteredChats) { chat in - NavigationLink(value: chat) { - ChatRow(chat: chat) + VStack(spacing: 0) { + List(selection: $selectedChat) { + ForEach(viewModel.filteredChats) { chat in + NavigationLink(value: chat) { + ChatRow(chat: chat) + } } } + ChatListStatusBar(networkState: networkState, typingState: typingState) } .navigationTitle("Chats") .searchable(text: $viewModel.searchText) @@ -178,6 +196,71 @@ public struct ChatListView: View { } } +public struct ChatListStatusBar: View { + public var networkState: NetworkState + public var typingState: TypingState + + public init(networkState: NetworkState, typingState: TypingState) { + self.networkState = networkState + self.typingState = typingState + } + + public var body: some View { + HStack(spacing: 8) { + Image(systemName: networkIconName) + .foregroundStyle(networkState == .ready ? .green : .orange) + Text(statusText) + .font(.footnote) + .lineLimit(1) + Spacer() + } + .padding(.horizontal, 12) + .padding(.vertical, 8) + .background(.bar) + } + + private var networkIconName: String { + switch networkState { + case .ready: + "checkmark.circle.fill" + case .waitingForNetwork: + "wifi.slash" + case .connectingToProxy: + "shield.lefthalf.filled" + case .connecting: + "antenna.radiowaves.left.and.right" + case .updating: + "arrow.triangle.2.circlepath" + } + } + + private var statusText: String { + switch typingState { + case let .typing(_, _, text) where !text.isEmpty: + text + case .typing: + "Typing" + case .idle: + networkText + } + } + + private var networkText: String { + switch networkState { + case .ready: + "Online" + case .waitingForNetwork: + "Waiting for network" + case .connectingToProxy: + "Connecting to proxy" + case .connecting: + "Connecting" + case .updating: + "Updating" + } + } +} + public struct ChatRow: View { public var chat: ChatSummary @@ -195,7 +278,20 @@ public struct ChatRow: View { Image(systemName: "pin.fill") .font(.caption) } + if chat.isMuted { + Image(systemName: "bell.slash") + .font(.caption) + .foregroundStyle(.secondary) + } Spacer() + if chat.unreadMentionCount > 0 { + Text("@\(chat.unreadMentionCount)") + .font(.caption) + .padding(.horizontal, 7) + .padding(.vertical, 3) + .background(.orange, in: Capsule()) + .foregroundStyle(.white) + } if chat.unreadCount > 0 { Text("\(chat.unreadCount)") .font(.caption) @@ -205,7 +301,13 @@ public struct ChatRow: View { .foregroundStyle(.white) } } - Text(chat.draft?.text ?? chat.lastMessage) + HStack(spacing: 4) { + if chat.draft != nil { + Text("Draft") + .fontWeight(.semibold) + } + Text(chat.draft?.text ?? chat.lastMessage) + } .font(.subheadline) .foregroundStyle(chat.draft == nil ? Color.secondary : Color.red) .lineLimit(2)