Gitfed
bastien-mrq/bt-message / btmessage / Views / ChatListView.swift
// ChatListView.swift

import SwiftUI

struct ChatListView: View {
    @EnvironmentObject var appState: AppState
    @State private var showCreateGroup = false

    var body: some View {
        List {
            // MARK: Pairs (DMs)
            if !appState.peers.isEmpty {
                Section("Contacts") {
                    ForEach(appState.peers) { peer in
                        NavigationLink(destination: ChatView(conversationID: peer.id, title: String(peer.displayName.prefix(8)).uppercased())) {
                            PeerRow(peer: peer, lastMessage: appState.conversations[peer.id]?.last)
                        }
                    }
                }
            }

            // MARK: Groupes
            if !appState.groups.isEmpty {
                Section("Groupes") {
                    ForEach(appState.groups) { group in
                        NavigationLink(destination: ChatView(conversationID: group.id, title: group.name)) {
                            GroupRow(group: group, lastMessage: appState.conversations[group.id]?.last)
                        }
                    }
                }
            }

            // MARK: Placeholder si vide
            if appState.peers.isEmpty && appState.groups.isEmpty {
                VStack(spacing: 12) {
                    Image(systemName: "antenna.radiowaves.left.and.right")
                        .font(.system(size: 44))
                        .foregroundColor(.secondary)
                    Text("Recherche de pairs…")
                        .foregroundColor(.secondary)
                    Text("Active Bluetooth et WiFi.\nLes appareils avec btmessage apparaîtront ici.")
                        .font(.caption)
                        .multilineTextAlignment(.center)
                        .foregroundColor(.secondary)
                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 40)
                .listRowBackground(Color.clear)
            }
        }
        .navigationTitle("btmessage")
        .toolbar {
            ToolbarItemGroup(placement: .navigationBarTrailing) {
                Button(action: { showCreateGroup = true }) {
                    Image(systemName: "person.3.fill")
                }
                ConnectionIndicator(count: appState.peers.count)
            }
        }
        .sheet(isPresented: $showCreateGroup) {
            CreateGroupView()
                .environmentObject(appState)
        }
        .alert("Erreur",
               isPresented: Binding(
                   get: { appState.errorMessage != nil },
                   set: { if !$0 { appState.errorMessage = nil } }
               )
        ) {
            Button("OK") { appState.errorMessage = nil }
        } message: {
            Text(appState.errorMessage ?? "")
        }
    }
}

// MARK: - PeerRow

private struct PeerRow: View {
    let peer: PeerInfo
    let lastMessage: ChatMessage?

    var body: some View {
        HStack(spacing: 12) {
            ZStack {
                Circle()
                    .fill(peer.isKeyKnown ? Color.green.opacity(0.2) : Color.orange.opacity(0.2))
                    .frame(width: 44, height: 44)
                Image(systemName: peer.isKeyKnown ? "lock.fill" : "lock.open")
                    .foregroundColor(peer.isKeyKnown ? .green : .orange)
            }

            VStack(alignment: .leading, spacing: 2) {
                HStack {
                    Text(String(peer.displayName.prefix(8)).uppercased())
                        .font(.headline)
                    Spacer()
                    if let msg = lastMessage {
                        Text(msg.timestamp, style: .time)
                            .font(.caption)
                            .foregroundColor(.secondary)
                    }
                }
                if let msg = lastMessage {
                    Text(msg.text)
                        .font(.subheadline)
                        .foregroundColor(.secondary)
                        .lineLimit(1)
                } else if peer.isKeyKnown {
                    Text("Clé PQC échangée")
                        .font(.caption)
                        .foregroundColor(.green)
                } else {
                    Text("Échange de clé en cours…")
                        .font(.caption)
                        .foregroundColor(.orange)
                }
            }
        }
        .padding(.vertical, 4)
    }
}

// MARK: - GroupRow

private struct GroupRow: View {
    let group: GroupModel
    let lastMessage: ChatMessage?

    var body: some View {
        HStack(spacing: 12) {
            ZStack {
                Circle()
                    .fill(Color.blue.opacity(0.15))
                    .frame(width: 44, height: 44)
                Image(systemName: "person.3.fill")
                    .font(.system(size: 16))
                    .foregroundColor(.blue)
            }

            VStack(alignment: .leading, spacing: 2) {
                HStack {
                    Text(group.name)
                        .font(.headline)
                    Spacer()
                    if let msg = lastMessage {
                        Text(msg.timestamp, style: .time)
                            .font(.caption)
                            .foregroundColor(.secondary)
                    }
                }
                if let msg = lastMessage {
                    Text(msg.text)
                        .font(.subheadline)
                        .foregroundColor(.secondary)
                        .lineLimit(1)
                } else {
                    Text("\(group.memberIDs.count) membres · chiffré AES-256")
                        .font(.caption)
                        .foregroundColor(.secondary)
                }
            }
        }
        .padding(.vertical, 4)
    }
}

// MARK: - ConnectionIndicator

private struct ConnectionIndicator: View {
    let count: Int

    var body: some View {
        HStack(spacing: 4) {
            Circle()
                .fill(count > 0 ? Color.green : Color.gray)
                .frame(width: 8, height: 8)
            Text("\(count)")
                .font(.caption)
                .foregroundColor(.secondary)
        }
    }
}