Signed-off-by: Hadi <[email protected]>
This commit is contained in:
Hadi
2026-08-13 00:58:37 +02:00
commit 59b31e5ca3
103 changed files with 8577 additions and 0 deletions
+296
View File
@@ -0,0 +1,296 @@
package network
import (
"fmt"
"strings"
"charm.land/bubbles/v2/key"
"charm.land/bubbles/v2/textinput"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
ilovetui "github.com/anotherhadi/ilovetui"
"github.com/anotherhadi/settuings/internal/icons"
"github.com/anotherhadi/settuings/internal/keys"
"github.com/anotherhadi/settuings/internal/network"
)
type wifiState struct {
loading bool
err error
ip string
device string // wifi interface name, e.g. "wlo1"
connectionName string // active connection profile name, e.g. "Djoon 1"
radioOn bool
current *network.WifiNetwork // the AP we're associated with, if any
others []network.WifiNetwork
cursor int
pending bool // a connect/disconnect/rescan/radio-toggle is in flight
feedback string
connecting bool
connectSSID string
password textinput.Model
}
func newWifiState() wifiState {
ti := textinput.New()
ti.Prompt = "Password: "
ti.EchoMode = textinput.EchoPassword
ti.EchoCharacter = '•'
s := ti.Styles()
s.Focused.Prompt = lipgloss.NewStyle().Foreground(ilovetui.S.Primary)
ti.SetStyles(s)
return wifiState{password: ti, loading: true}
}
func (m Model) isKnownSSID(ssid string) bool {
for _, k := range m.known.networks {
if k.Name == ssid {
return true
}
}
return false
}
func (m *Model) handleWifiList(msg wifiListMsg) {
m.wifi.loading = false
m.wifi.err = msg.err
m.wifi.radioOn = msg.radioOn
if msg.err != nil {
return
}
m.wifi.ip = msg.ip
m.wifi.device = msg.device
m.wifi.connectionName = msg.connectionName
m.wifi.current = nil
m.wifi.others = m.wifi.others[:0]
for _, n := range msg.networks {
n := n
if n.Active {
m.wifi.current = &n
continue
}
m.wifi.others = append(m.wifi.others, n)
}
if m.wifi.cursor >= len(m.wifi.others) {
m.wifi.cursor = max(0, len(m.wifi.others)-1)
}
}
func (m *Model) handleWifiAction(msg wifiActionMsg) tea.Cmd {
m.wifi.pending = false
if msg.err != nil {
m.wifi.feedback = msg.err.Error()
m.deviceDetail.pending = false
m.deviceDetail.feedback = msg.err.Error()
m.refreshDeviceDetailContent()
return nil
}
m.wifi.feedback = ""
m.closeDeviceDetailIfPending(tabWifi)
return tea.Batch(fetchWifiCmd(), fetchKnownCmd())
}
func (m *Model) handleRadioAction(msg radioActionMsg) tea.Cmd {
m.wifi.pending = false
if msg.err != nil {
m.wifi.feedback = msg.err.Error()
return nil
}
m.wifi.feedback = ""
return fetchWifiCmd()
}
func (m *Model) updateWifi(msg tea.Msg) tea.Cmd {
if m.wifi.connecting {
return m.updateWifiPasswordPrompt(msg)
}
keyMsg, ok := msg.(tea.KeyPressMsg)
if !ok {
return nil
}
g := keys.Keys.Global
n := keys.Keys.Network
switch {
case key.Matches(keyMsg, g.Up):
if m.wifi.cursor > 0 {
m.wifi.cursor--
}
case key.Matches(keyMsg, g.Down):
if m.wifi.cursor < len(m.wifi.others)-1 {
m.wifi.cursor++
}
case key.Matches(keyMsg, n.Refresh):
m.wifi.pending = true
return tea.Batch(rescanWifiCmd(), fetchWifiCmd())
case key.Matches(keyMsg, n.Select):
return m.selectWifi()
case key.Matches(keyMsg, n.ToggleRadio):
m.wifi.pending = true
return toggleWifiRadioCmd(!m.wifi.radioOn)
case key.Matches(keyMsg, n.Disconnect):
if m.wifi.current == nil {
m.wifi.feedback = "Not connected to a network."
return nil
}
m.wifi.pending = true
return disconnectWifiCmd()
case key.Matches(keyMsg, n.Info):
if m.wifi.current == nil {
m.wifi.feedback = "Not connected to a network."
return nil
}
cur := m.wifi.current
return m.openDeviceDetail(tabWifi, m.wifi.device, cur.SSID, cur.Security, cur.Signal)
}
return nil
}
func (m *Model) selectWifi() tea.Cmd {
if len(m.wifi.others) == 0 {
return nil
}
target := m.wifi.others[m.wifi.cursor]
if target.Secured() && !m.isKnownSSID(target.SSID) {
m.wifi.connecting = true
m.wifi.connectSSID = target.SSID
m.wifi.password.SetValue("")
m.wifi.password.Focus()
return nil
}
m.wifi.pending = true
return connectWifiCmd(target.SSID, "")
}
func (m *Model) updateWifiPasswordPrompt(msg tea.Msg) tea.Cmd {
keyMsg, ok := msg.(tea.KeyPressMsg)
if !ok {
return nil
}
g := keys.Keys.Global
switch {
case key.Matches(keyMsg, g.Escape):
m.wifi.connecting = false
m.wifi.password.Blur()
return nil
case keyMsg.String() == "enter":
ssid := m.wifi.connectSSID
password := m.wifi.password.Value()
m.wifi.connecting = false
m.wifi.password.Blur()
m.wifi.pending = true
return connectWifiCmd(ssid, password)
default:
var cmd tea.Cmd
m.wifi.password, cmd = m.wifi.password.Update(keyMsg)
return cmd
}
}
func (m Model) renderWifi(width, height int) string {
if m.wifi.connecting {
return m.renderWifiPasswordPrompt()
}
if m.wifi.loading {
return ilovetui.S.Faint.Render("Loading Wi-Fi networks…")
}
othersHeader := lipgloss.NewStyle().Bold(true).Foreground(ilovetui.S.Subtle).Render("OTHER NETWORKS")
if m.wifi.pending {
othersHeader += mutedStyle().Render(" refreshing…")
}
fixed := lipgloss.JoinVertical(lipgloss.Left,
m.renderWifiRadioStatus(),
"",
lipgloss.NewStyle().Bold(true).Foreground(ilovetui.S.Subtle).Render("CURRENT NETWORK"),
"",
m.renderCurrentWifiCard(width),
"",
othersHeader,
"",
)
fixedH := lipgloss.Height(fixed)
if m.wifi.err != nil {
return lipgloss.JoinVertical(lipgloss.Left, fixed, errorStyle().Render(m.wifi.err.Error()))
}
if len(m.wifi.others) == 0 {
return lipgloss.JoinVertical(lipgloss.Left, fixed, ilovetui.S.Faint.Render("No other networks in range."))
}
available := height - fixedH - 1 // reserve a trailing line for the paginator/feedback
if available < 1 {
available = 1
}
p, start, end := paginate(m.wifi.cursor, len(m.wifi.others), available)
var rows []string
for i := start; i < end; i++ {
rows = append(rows, m.renderWifiRow(m.wifi.others[i], i == m.wifi.cursor))
}
list := strings.Join(rows, "\n")
footer := renderPaginatorDots(p)
if m.wifi.feedback != "" {
footer = errorStyle().Render(m.wifi.feedback)
}
return lipgloss.JoinVertical(lipgloss.Left, fixed, list, footer)
}
func (m Model) renderWifiRadioStatus() string {
label := "Wi-Fi: "
if m.wifi.radioOn {
return label + successStyle().Bold(true).Render("On")
}
return label + errorStyle().Bold(true).Render("Off")
}
func (m Model) renderCurrentWifiCard(width int) string {
if m.wifi.current == nil {
return ilovetui.S.Faint.Render("Not connected")
}
cur := m.wifi.current
check := lipgloss.NewStyle().Foreground(ilovetui.S.Success).Render(icons.I.Check)
title := lipgloss.NewStyle().Bold(true).Render(cur.SSID)
line1 := check + title
line2 := lipgloss.NewStyle().Foreground(ilovetui.S.Subtle).Render(
fmt.Sprintf("%s · %d%%", securityLabel(cur.Security), cur.Signal))
if m.wifi.ip != "" {
line2 += lipgloss.NewStyle().Foreground(ilovetui.S.Subtle).Render(" · " + m.wifi.ip)
}
return lipgloss.NewStyle().Width(width).Render(lipgloss.JoinVertical(lipgloss.Left, line1, line2))
}
func (m Model) renderWifiRow(n network.WifiNetwork, selected bool) string {
textStyle := lipgloss.NewStyle().Foreground(ilovetui.S.Text)
subtleStyle := lipgloss.NewStyle().Foreground(ilovetui.S.Subtle)
if selected {
textStyle = textStyle.Bold(true).Foreground(ilovetui.S.Primary)
}
lock := ""
if n.Secured() {
lock = " " + icons.I.Lock
}
cursor := " "
if selected {
cursor = "> "
}
return fmt.Sprintf("%s%s%s %s", cursor, textStyle.Render(n.SSID), lock, subtleStyle.Render(fmt.Sprintf("%d%%", n.Signal)))
}
func (m Model) renderWifiPasswordPrompt() string {
title := lipgloss.NewStyle().Bold(true).Render("Connect to " + m.wifi.connectSSID)
return lipgloss.JoinVertical(lipgloss.Left, title, "", m.wifi.password.View())
}
func securityLabel(security string) string {
if security == "" || security == "--" {
return "Open"
}
return security
}