mirror of
https://github.com/anotherhadi/settuings.git
synced 2026-10-05 16:18:24 +02:00
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user