feat(tui): add encryption selection screen between disk and kernel steps
- commit
- 0f6eb900c19ccd1194a4bcbcda5ce7376b9cb01b
- author
- brice · · 2026-02-27 05:31 UTC
- parent
- 81998aa
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +198 -2
M internal/interfaces/tui/app.go +70 -2
| @@ -30,9 +30,11 @@ type App struct { | |||
| 30 | 30 | // UI models (using concrete types for compatibility with views/handlers) | |
| 31 | 31 | formModel *models.FormModelImpl | |
| 32 | 32 | diskModel *models.DiskModelImpl | |
| 33 | + | encryptionModel *models.EncryptionModelImpl | |
| 33 | 34 | kernelModel *models.KernelModelImpl | |
| 34 | 35 | amdPstateModel *models.AMDPStateModelImpl | |
| 35 | 36 | gpuModel *models.GPUModelImpl | |
| 37 | + | reposModel *models.ReposModelImpl | |
| 36 | 38 | dankLinuxModel *models.DankLinuxModelImpl | |
| 37 | 39 | installationModel *models.InstallationModelImpl | |
| 38 | 40 | progressModel *models.ProgressModelImpl | |
| @@ -55,9 +57,11 @@ type Screen string | |||
| 55 | 57 | const ( | |
| 56 | 58 | ScreenForm Screen = "form" | |
| 57 | 59 | ScreenDisk Screen = "disk" | |
| 60 | + | ScreenEncryption Screen = "encryption" | |
| 58 | 61 | ScreenKernel Screen = "kernel" | |
| 59 | 62 | ScreenAMDPState Screen = "amd-pstate" | |
| 60 | 63 | ScreenGPU Screen = "gpu" | |
| 64 | + | ScreenRepos Screen = "repos" | |
| 61 | 65 | ScreenDankLinux Screen = "danklinux" | |
| 62 | 66 | ScreenInstalling Screen = "installing" | |
| 63 | 67 | ScreenProgress Screen = "progress" | |
| @@ -81,9 +85,11 @@ func NewApp( | |||
| 81 | 85 | logger: logger, | |
| 82 | 86 | formModel: models.NewFormModel(), | |
| 83 | 87 | diskModel: models.NewDiskModel(), | |
| 88 | + | encryptionModel: models.NewEncryptionModel(), | |
| 84 | 89 | kernelModel: models.NewKernelModel(), | |
| 85 | 90 | amdPstateModel: models.NewAMDPStateModel(), | |
| 86 | 91 | gpuModel: models.NewGPUModel(), | |
| 92 | + | reposModel: models.NewReposModel(), | |
| 87 | 93 | dankLinuxModel: models.NewDankLinuxModel(), | |
| 88 | 94 | installationModel: models.NewInstallationModel(), | |
| 89 | 95 | progressModel: models.NewProgressModel(), | |
| @@ -155,12 +161,16 @@ func (a *App) View() string { | |||
| 155 | 161 | return views.RenderForm(a.formModel) | |
| 156 | 162 | case ScreenDisk: | |
| 157 | 163 | return views.RenderDiskSelection(a.diskModel) | |
| 164 | + | case ScreenEncryption: | |
| 165 | + | return views.RenderEncryptionSelection(a.encryptionModel) | |
| 158 | 166 | case ScreenKernel: | |
| 159 | 167 | return views.RenderKernelSelection(a.kernelModel) | |
| 160 | 168 | case ScreenAMDPState: | |
| 161 | 169 | return views.RenderAMDPStateSelection(a.amdPstateModel) | |
| 162 | 170 | case ScreenGPU: | |
| 163 | 171 | return views.RenderGPUSelection(a.gpuModel) | |
| 172 | + | case ScreenRepos: | |
| 173 | + | return views.RenderReposSelection(a.reposModel) | |
| 164 | 174 | case ScreenDankLinux: | |
| 165 | 175 | return views.RenderDankLinuxSelection(a.dankLinuxModel) | |
| 166 | 176 | case ScreenInstalling, ScreenProgress: | |
| @@ -181,12 +191,16 @@ func (a *App) handleKeyMsg(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |||
| 181 | 191 | return a.handleFormInput(msg) | |
| 182 | 192 | case ScreenDisk: | |
| 183 | 193 | return a.handleDiskInput(msg) | |
| 194 | + | case ScreenEncryption: | |
| 195 | + | return a.handleEncryptionInput(msg) | |
| 184 | 196 | case ScreenKernel: | |
| 185 | 197 | return a.handleKernelInput(msg) | |
| 186 | 198 | case ScreenAMDPState: | |
| 187 | 199 | return a.handleAMDPStateInput(msg) | |
| 188 | 200 | case ScreenGPU: | |
| 189 | 201 | return a.handleGPUInput(msg) | |
| 202 | + | case ScreenRepos: | |
| 203 | + | return a.handleReposInput(msg) | |
| 190 | 204 | case ScreenDankLinux: | |
| 191 | 205 | return a.handleDankLinuxInput(msg) | |
| 192 | 206 | case ScreenProgress: | |
| @@ -317,8 +331,7 @@ func (a *App) handleGPUInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |||
| 317 | 331 | // Update stored form data directly | |
| 318 | 332 | a.formData.GPUVendor = string(selected.Vendor) | |
| 319 | 333 | a.formData.GPUDrivers = append([]string{}, selected.Drivers...) | |
| 320 | - | a.currentScreen = ScreenDankLinux | |
| 321 | - | return a, nil | |
| 334 | + | return a.startReposSelection() | |
| 322 | 335 | } | |
| 323 | 336 | ||
| 324 | 337 | return a, nil | |
| @@ -326,6 +339,8 @@ func (a *App) handleGPUInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |||
| 326 | 339 | ||
| 327 | 340 | func (a *App) handleDankLinuxInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |
| 328 | 341 | switch msg.String() { | |
| 342 | + | case "esc", "backspace": | |
| 343 | + | return a.startReposSelection() | |
| 329 | 344 | case "up", "shift+tab": | |
| 330 | 345 | a.dankLinuxModel.MoveUp() | |
| 331 | 346 | return a, nil | |
| @@ -341,6 +356,35 @@ func (a *App) handleDankLinuxInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |||
| 341 | 356 | return a, nil | |
| 342 | 357 | } | |
| 343 | 358 | ||
| 359 | + | func (a *App) startReposSelection() (tea.Model, tea.Cmd) { | |
| 360 | + | a.currentScreen = ScreenRepos | |
| 361 | + | return a, nil | |
| 362 | + | } | |
| 363 | + | ||
| 364 | + | func (a *App) handleReposInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |
| 365 | + | switch msg.String() { | |
| 366 | + | case "ctrl+c", "q": | |
| 367 | + | return a, tea.Quit | |
| 368 | + | case "esc", "backspace": | |
| 369 | + | return a.startGPUSelection() | |
| 370 | + | case "up": | |
| 371 | + | a.reposModel.MoveUp() | |
| 372 | + | return a, nil | |
| 373 | + | case "down": | |
| 374 | + | a.reposModel.MoveDown() | |
| 375 | + | return a, nil | |
| 376 | + | case "tab", "shift+tab": | |
| 377 | + | a.reposModel.NextSection() | |
| 378 | + | return a, nil | |
| 379 | + | case "enter": | |
| 380 | + | a.formData.AURHelper = a.reposModel.SelectedAURHelper() | |
| 381 | + | a.formData.EnableChaotic = a.reposModel.SelectedChaoticEnabled() | |
| 382 | + | a.currentScreen = ScreenDankLinux | |
| 383 | + | return a, nil | |
| 384 | + | } | |
| 385 | + | return a, nil | |
| 386 | + | } | |
| 387 | + | ||
| 344 | 388 | func (a *App) handleKernelInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |
| 345 | 389 | switch msg.String() { | |
| 346 | 390 | case "ctrl+c": | |
| @@ -536,6 +580,30 @@ func (a *App) handleDiskInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |||
| 536 | 580 | } | |
| 537 | 581 | // Update stored form data directly | |
| 538 | 582 | a.formData.TargetDisk = selected.Path | |
| 583 | + | return a.startEncryptionSelection() | |
| 584 | + | } | |
| 585 | + | return a, nil | |
| 586 | + | } | |
| 587 | + | ||
| 588 | + | func (a *App) startEncryptionSelection() (tea.Model, tea.Cmd) { | |
| 589 | + | a.currentScreen = ScreenEncryption | |
| 590 | + | return a, nil | |
| 591 | + | } | |
| 592 | + | ||
| 593 | + | func (a *App) handleEncryptionInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |
| 594 | + | switch msg.String() { | |
| 595 | + | case "ctrl+c": | |
| 596 | + | return a, tea.Quit | |
| 597 | + | case "esc", "backspace": | |
| 598 | + | return a.startDiskSelection() | |
| 599 | + | case "up", "shift+tab": | |
| 600 | + | a.encryptionModel.MoveUp() | |
| 601 | + | return a, nil | |
| 602 | + | case "down", "tab": | |
| 603 | + | a.encryptionModel.MoveDown() | |
| 604 | + | return a, nil | |
| 605 | + | case "enter": | |
| 606 | + | a.formData.EncryptionType = a.encryptionModel.SelectedOption().Value | |
| 539 | 607 | return a.startKernelSelection() | |
| 540 | 608 | } | |
| 541 | 609 | return a, nil | |
| @@ -30,9 +30,11 @@ type App struct { | |||||
| 30 | // UI models (using concrete types for compatibility with views/handlers) | 30 | // UI models (using concrete types for compatibility with views/handlers) | ||
| 31 | formModel *models.FormModelImpl | 31 | formModel *models.FormModelImpl | ||
| 32 | diskModel *models.DiskModelImpl | 32 | diskModel *models.DiskModelImpl | ||
| 33 | + | encryptionModel *models.EncryptionModelImpl | |||
| 33 | kernelModel *models.KernelModelImpl | 34 | kernelModel *models.KernelModelImpl | ||
| 34 | amdPstateModel *models.AMDPStateModelImpl | 35 | amdPstateModel *models.AMDPStateModelImpl | ||
| 35 | gpuModel *models.GPUModelImpl | 36 | gpuModel *models.GPUModelImpl | ||
| 37 | + | reposModel *models.ReposModelImpl | |||
| 36 | dankLinuxModel *models.DankLinuxModelImpl | 38 | dankLinuxModel *models.DankLinuxModelImpl | ||
| 37 | installationModel *models.InstallationModelImpl | 39 | installationModel *models.InstallationModelImpl | ||
| 38 | progressModel *models.ProgressModelImpl | 40 | progressModel *models.ProgressModelImpl | ||
| @@ -55,9 +57,11 @@ type Screen string | |||||
| 55 | const ( | 57 | const ( | ||
| 56 | ScreenForm Screen = "form" | 58 | ScreenForm Screen = "form" | ||
| 57 | ScreenDisk Screen = "disk" | 59 | ScreenDisk Screen = "disk" | ||
| 60 | + | ScreenEncryption Screen = "encryption" | |||
| 58 | ScreenKernel Screen = "kernel" | 61 | ScreenKernel Screen = "kernel" | ||
| 59 | ScreenAMDPState Screen = "amd-pstate" | 62 | ScreenAMDPState Screen = "amd-pstate" | ||
| 60 | ScreenGPU Screen = "gpu" | 63 | ScreenGPU Screen = "gpu" | ||
| 64 | + | ScreenRepos Screen = "repos" | |||
| 61 | ScreenDankLinux Screen = "danklinux" | 65 | ScreenDankLinux Screen = "danklinux" | ||
| 62 | ScreenInstalling Screen = "installing" | 66 | ScreenInstalling Screen = "installing" | ||
| 63 | ScreenProgress Screen = "progress" | 67 | ScreenProgress Screen = "progress" | ||
| @@ -81,9 +85,11 @@ func NewApp( | |||||
| 81 | logger: logger, | 85 | logger: logger, | ||
| 82 | formModel: models.NewFormModel(), | 86 | formModel: models.NewFormModel(), | ||
| 83 | diskModel: models.NewDiskModel(), | 87 | diskModel: models.NewDiskModel(), | ||
| 88 | + | encryptionModel: models.NewEncryptionModel(), | |||
| 84 | kernelModel: models.NewKernelModel(), | 89 | kernelModel: models.NewKernelModel(), | ||
| 85 | amdPstateModel: models.NewAMDPStateModel(), | 90 | amdPstateModel: models.NewAMDPStateModel(), | ||
| 86 | gpuModel: models.NewGPUModel(), | 91 | gpuModel: models.NewGPUModel(), | ||
| 92 | + | reposModel: models.NewReposModel(), | |||
| 87 | dankLinuxModel: models.NewDankLinuxModel(), | 93 | dankLinuxModel: models.NewDankLinuxModel(), | ||
| 88 | installationModel: models.NewInstallationModel(), | 94 | installationModel: models.NewInstallationModel(), | ||
| 89 | progressModel: models.NewProgressModel(), | 95 | progressModel: models.NewProgressModel(), | ||
| @@ -155,12 +161,16 @@ func (a *App) View() string { | |||||
| 155 | return views.RenderForm(a.formModel) | 161 | return views.RenderForm(a.formModel) | ||
| 156 | case ScreenDisk: | 162 | case ScreenDisk: | ||
| 157 | return views.RenderDiskSelection(a.diskModel) | 163 | return views.RenderDiskSelection(a.diskModel) | ||
| 164 | + | case ScreenEncryption: | |||
| 165 | + | return views.RenderEncryptionSelection(a.encryptionModel) | |||
| 158 | case ScreenKernel: | 166 | case ScreenKernel: | ||
| 159 | return views.RenderKernelSelection(a.kernelModel) | 167 | return views.RenderKernelSelection(a.kernelModel) | ||
| 160 | case ScreenAMDPState: | 168 | case ScreenAMDPState: | ||
| 161 | return views.RenderAMDPStateSelection(a.amdPstateModel) | 169 | return views.RenderAMDPStateSelection(a.amdPstateModel) | ||
| 162 | case ScreenGPU: | 170 | case ScreenGPU: | ||
| 163 | return views.RenderGPUSelection(a.gpuModel) | 171 | return views.RenderGPUSelection(a.gpuModel) | ||
| 172 | + | case ScreenRepos: | |||
| 173 | + | return views.RenderReposSelection(a.reposModel) | |||
| 164 | case ScreenDankLinux: | 174 | case ScreenDankLinux: | ||
| 165 | return views.RenderDankLinuxSelection(a.dankLinuxModel) | 175 | return views.RenderDankLinuxSelection(a.dankLinuxModel) | ||
| 166 | case ScreenInstalling, ScreenProgress: | 176 | case ScreenInstalling, ScreenProgress: | ||
| @@ -181,12 +191,16 @@ func (a *App) handleKeyMsg(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |||||
| 181 | return a.handleFormInput(msg) | 191 | return a.handleFormInput(msg) | ||
| 182 | case ScreenDisk: | 192 | case ScreenDisk: | ||
| 183 | return a.handleDiskInput(msg) | 193 | return a.handleDiskInput(msg) | ||
| 194 | + | case ScreenEncryption: | |||
| 195 | + | return a.handleEncryptionInput(msg) | |||
| 184 | case ScreenKernel: | 196 | case ScreenKernel: | ||
| 185 | return a.handleKernelInput(msg) | 197 | return a.handleKernelInput(msg) | ||
| 186 | case ScreenAMDPState: | 198 | case ScreenAMDPState: | ||
| 187 | return a.handleAMDPStateInput(msg) | 199 | return a.handleAMDPStateInput(msg) | ||
| 188 | case ScreenGPU: | 200 | case ScreenGPU: | ||
| 189 | return a.handleGPUInput(msg) | 201 | return a.handleGPUInput(msg) | ||
| 202 | + | case ScreenRepos: | |||
| 203 | + | return a.handleReposInput(msg) | |||
| 190 | case ScreenDankLinux: | 204 | case ScreenDankLinux: | ||
| 191 | return a.handleDankLinuxInput(msg) | 205 | return a.handleDankLinuxInput(msg) | ||
| 192 | case ScreenProgress: | 206 | case ScreenProgress: | ||
| @@ -317,8 +331,7 @@ func (a *App) handleGPUInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |||||
| 317 | // Update stored form data directly | 331 | // Update stored form data directly | ||
| 318 | a.formData.GPUVendor = string(selected.Vendor) | 332 | a.formData.GPUVendor = string(selected.Vendor) | ||
| 319 | a.formData.GPUDrivers = append([]string{}, selected.Drivers...) | 333 | a.formData.GPUDrivers = append([]string{}, selected.Drivers...) | ||
| 320 | - | a.currentScreen = ScreenDankLinux | 334 | + | return a.startReposSelection() |
| 321 | - | return a, nil | |||
| 322 | } | 335 | } | ||
| 323 | 336 | ||||
| 324 | return a, nil | 337 | return a, nil | ||
| @@ -326,6 +339,8 @@ func (a *App) handleGPUInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |||||
| 326 | 339 | ||||
| 327 | func (a *App) handleDankLinuxInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | 340 | func (a *App) handleDankLinuxInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | ||
| 328 | switch msg.String() { | 341 | switch msg.String() { | ||
| 342 | + | case "esc", "backspace": | |||
| 343 | + | return a.startReposSelection() | |||
| 329 | case "up", "shift+tab": | 344 | case "up", "shift+tab": | ||
| 330 | a.dankLinuxModel.MoveUp() | 345 | a.dankLinuxModel.MoveUp() | ||
| 331 | return a, nil | 346 | return a, nil | ||
| @@ -341,6 +356,35 @@ func (a *App) handleDankLinuxInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |||||
| 341 | return a, nil | 356 | return a, nil | ||
| 342 | } | 357 | } | ||
| 343 | 358 | ||||
| 359 | + | func (a *App) startReposSelection() (tea.Model, tea.Cmd) { | |||
| 360 | + | a.currentScreen = ScreenRepos | |||
| 361 | + | return a, nil | |||
| 362 | + | } | |||
| 363 | + | ||||
| 364 | + | func (a *App) handleReposInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |||
| 365 | + | switch msg.String() { | |||
| 366 | + | case "ctrl+c", "q": | |||
| 367 | + | return a, tea.Quit | |||
| 368 | + | case "esc", "backspace": | |||
| 369 | + | return a.startGPUSelection() | |||
| 370 | + | case "up": | |||
| 371 | + | a.reposModel.MoveUp() | |||
| 372 | + | return a, nil | |||
| 373 | + | case "down": | |||
| 374 | + | a.reposModel.MoveDown() | |||
| 375 | + | return a, nil | |||
| 376 | + | case "tab", "shift+tab": | |||
| 377 | + | a.reposModel.NextSection() | |||
| 378 | + | return a, nil | |||
| 379 | + | case "enter": | |||
| 380 | + | a.formData.AURHelper = a.reposModel.SelectedAURHelper() | |||
| 381 | + | a.formData.EnableChaotic = a.reposModel.SelectedChaoticEnabled() | |||
| 382 | + | a.currentScreen = ScreenDankLinux | |||
| 383 | + | return a, nil | |||
| 384 | + | } | |||
| 385 | + | return a, nil | |||
| 386 | + | } | |||
| 387 | + | ||||
| 344 | func (a *App) handleKernelInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | 388 | func (a *App) handleKernelInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | ||
| 345 | switch msg.String() { | 389 | switch msg.String() { | ||
| 346 | case "ctrl+c": | 390 | case "ctrl+c": | ||
| @@ -536,6 +580,30 @@ func (a *App) handleDiskInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |||||
| 536 | } | 580 | } | ||
| 537 | // Update stored form data directly | 581 | // Update stored form data directly | ||
| 538 | a.formData.TargetDisk = selected.Path | 582 | a.formData.TargetDisk = selected.Path | ||
| 583 | + | return a.startEncryptionSelection() | |||
| 584 | + | } | |||
| 585 | + | return a, nil | |||
| 586 | + | } | |||
| 587 | + | ||||
| 588 | + | func (a *App) startEncryptionSelection() (tea.Model, tea.Cmd) { | |||
| 589 | + | a.currentScreen = ScreenEncryption | |||
| 590 | + | return a, nil | |||
| 591 | + | } | |||
| 592 | + | ||||
| 593 | + | func (a *App) handleEncryptionInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) { | |||
| 594 | + | switch msg.String() { | |||
| 595 | + | case "ctrl+c": | |||
| 596 | + | return a, tea.Quit | |||
| 597 | + | case "esc", "backspace": | |||
| 598 | + | return a.startDiskSelection() | |||
| 599 | + | case "up", "shift+tab": | |||
| 600 | + | a.encryptionModel.MoveUp() | |||
| 601 | + | return a, nil | |||
| 602 | + | case "down", "tab": | |||
| 603 | + | a.encryptionModel.MoveDown() | |||
| 604 | + | return a, nil | |||
| 605 | + | case "enter": | |||
| 606 | + | a.formData.EncryptionType = a.encryptionModel.SelectedOption().Value | |||
| 539 | return a.startKernelSelection() | 607 | return a.startKernelSelection() | ||
| 540 | } | 608 | } | ||
| 541 | return a, nil | 609 | return a, nil | ||
A internal/interfaces/tui/models/encryption.go +78 -0
| @@ -0,0 +1,78 @@ | |||
| 1 | + | package models | |
| 2 | + | ||
| 3 | + | // EncryptionOption represents a selectable encryption option. | |
| 4 | + | type EncryptionOption struct { | |
| 5 | + | Value string | |
| 6 | + | Label string | |
| 7 | + | Description string | |
| 8 | + | Recommended bool | |
| 9 | + | } | |
| 10 | + | ||
| 11 | + | // EncryptionModelImpl holds encryption selection state. | |
| 12 | + | type EncryptionModelImpl struct { | |
| 13 | + | options []EncryptionOption | |
| 14 | + | selected int | |
| 15 | + | } | |
| 16 | + | ||
| 17 | + | // NewEncryptionModel creates a new encryption selection model. | |
| 18 | + | func NewEncryptionModel() *EncryptionModelImpl { | |
| 19 | + | options := []EncryptionOption{ | |
| 20 | + | {Value: "luks", Label: "LUKS2", Description: "Full disk encryption (recommended)", Recommended: true}, | |
| 21 | + | {Value: "none", Label: "None", Description: "No encryption"}, | |
| 22 | + | } | |
| 23 | + | selected := 0 | |
| 24 | + | for i, o := range options { | |
| 25 | + | if o.Recommended { | |
| 26 | + | selected = i | |
| 27 | + | break | |
| 28 | + | } | |
| 29 | + | } | |
| 30 | + | return &EncryptionModelImpl{options: options, selected: selected} | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | // Options returns the selectable encryption options. | |
| 34 | + | func (em *EncryptionModelImpl) Options() []EncryptionOption { return em.options } | |
| 35 | + | ||
| 36 | + | // SelectedIndex returns the current selection index. | |
| 37 | + | func (em *EncryptionModelImpl) SelectedIndex() int { return em.selected } | |
| 38 | + | ||
| 39 | + | // SelectedOption returns the currently selected option. | |
| 40 | + | func (em *EncryptionModelImpl) SelectedOption() EncryptionOption { | |
| 41 | + | if len(em.options) == 0 { | |
| 42 | + | return EncryptionOption{} | |
| 43 | + | } | |
| 44 | + | if em.selected < 0 || em.selected >= len(em.options) { | |
| 45 | + | return em.options[0] | |
| 46 | + | } | |
| 47 | + | return em.options[em.selected] | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | // MoveUp moves selection up (wraps). | |
| 51 | + | func (em *EncryptionModelImpl) MoveUp() { | |
| 52 | + | if len(em.options) == 0 { | |
| 53 | + | return | |
| 54 | + | } | |
| 55 | + | if em.selected == 0 { | |
| 56 | + | em.selected = len(em.options) - 1 | |
| 57 | + | return | |
| 58 | + | } | |
| 59 | + | em.selected-- | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | // MoveDown moves selection down (wraps). | |
| 63 | + | func (em *EncryptionModelImpl) MoveDown() { | |
| 64 | + | if len(em.options) == 0 { | |
| 65 | + | return | |
| 66 | + | } | |
| 67 | + | em.selected = (em.selected + 1) % len(em.options) | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | // SetSelectedValue selects an option by value string. | |
| 71 | + | func (em *EncryptionModelImpl) SetSelectedValue(v string) { | |
| 72 | + | for i, o := range em.options { | |
| 73 | + | if o.Value == v { | |
| 74 | + | em.selected = i | |
| 75 | + | return | |
| 76 | + | } | |
| 77 | + | } | |
| 78 | + | } | |
| @@ -0,0 +1,78 @@ | |||||
| 1 | + | package models | |||
| 2 | + | ||||
| 3 | + | // EncryptionOption represents a selectable encryption option. | |||
| 4 | + | type EncryptionOption struct { | |||
| 5 | + | Value string | |||
| 6 | + | Label string | |||
| 7 | + | Description string | |||
| 8 | + | Recommended bool | |||
| 9 | + | } | |||
| 10 | + | ||||
| 11 | + | // EncryptionModelImpl holds encryption selection state. | |||
| 12 | + | type EncryptionModelImpl struct { | |||
| 13 | + | options []EncryptionOption | |||
| 14 | + | selected int | |||
| 15 | + | } | |||
| 16 | + | ||||
| 17 | + | // NewEncryptionModel creates a new encryption selection model. | |||
| 18 | + | func NewEncryptionModel() *EncryptionModelImpl { | |||
| 19 | + | options := []EncryptionOption{ | |||
| 20 | + | {Value: "luks", Label: "LUKS2", Description: "Full disk encryption (recommended)", Recommended: true}, | |||
| 21 | + | {Value: "none", Label: "None", Description: "No encryption"}, | |||
| 22 | + | } | |||
| 23 | + | selected := 0 | |||
| 24 | + | for i, o := range options { | |||
| 25 | + | if o.Recommended { | |||
| 26 | + | selected = i | |||
| 27 | + | break | |||
| 28 | + | } | |||
| 29 | + | } | |||
| 30 | + | return &EncryptionModelImpl{options: options, selected: selected} | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | // Options returns the selectable encryption options. | |||
| 34 | + | func (em *EncryptionModelImpl) Options() []EncryptionOption { return em.options } | |||
| 35 | + | ||||
| 36 | + | // SelectedIndex returns the current selection index. | |||
| 37 | + | func (em *EncryptionModelImpl) SelectedIndex() int { return em.selected } | |||
| 38 | + | ||||
| 39 | + | // SelectedOption returns the currently selected option. | |||
| 40 | + | func (em *EncryptionModelImpl) SelectedOption() EncryptionOption { | |||
| 41 | + | if len(em.options) == 0 { | |||
| 42 | + | return EncryptionOption{} | |||
| 43 | + | } | |||
| 44 | + | if em.selected < 0 || em.selected >= len(em.options) { | |||
| 45 | + | return em.options[0] | |||
| 46 | + | } | |||
| 47 | + | return em.options[em.selected] | |||
| 48 | + | } | |||
| 49 | + | ||||
| 50 | + | // MoveUp moves selection up (wraps). | |||
| 51 | + | func (em *EncryptionModelImpl) MoveUp() { | |||
| 52 | + | if len(em.options) == 0 { | |||
| 53 | + | return | |||
| 54 | + | } | |||
| 55 | + | if em.selected == 0 { | |||
| 56 | + | em.selected = len(em.options) - 1 | |||
| 57 | + | return | |||
| 58 | + | } | |||
| 59 | + | em.selected-- | |||
| 60 | + | } | |||
| 61 | + | ||||
| 62 | + | // MoveDown moves selection down (wraps). | |||
| 63 | + | func (em *EncryptionModelImpl) MoveDown() { | |||
| 64 | + | if len(em.options) == 0 { | |||
| 65 | + | return | |||
| 66 | + | } | |||
| 67 | + | em.selected = (em.selected + 1) % len(em.options) | |||
| 68 | + | } | |||
| 69 | + | ||||
| 70 | + | // SetSelectedValue selects an option by value string. | |||
| 71 | + | func (em *EncryptionModelImpl) SetSelectedValue(v string) { | |||
| 72 | + | for i, o := range em.options { | |||
| 73 | + | if o.Value == v { | |||
| 74 | + | em.selected = i | |||
| 75 | + | return | |||
| 76 | + | } | |||
| 77 | + | } | |||
| 78 | + | } | |||
M internal/interfaces/tui/models/form.go +2 -0
| @@ -40,6 +40,7 @@ type FormData struct { | |||
| 40 | 40 | AURHelper string | |
| 41 | 41 | Microcode bool | |
| 42 | 42 | InstallDankLinux bool | |
| 43 | + | EnableChaotic bool | |
| 43 | 44 | } | |
| 44 | 45 | ||
| 45 | 46 | // FormModelImpl implements FormModel interface | |
| @@ -312,6 +313,7 @@ func (fm *FormModelImpl) extractData() { | |||
| 312 | 313 | Locale: fm.fields[5].Value(), | |
| 313 | 314 | Keymap: fm.fields[6].Value(), | |
| 314 | 315 | InstallDankLinux: fm.data.InstallDankLinux, | |
| 316 | + | EnableChaotic: fm.data.EnableChaotic, | |
| 315 | 317 | } | |
| 316 | 318 | } | |
| 317 | 319 | ||
| @@ -40,6 +40,7 @@ type FormData struct { | |||||
| 40 | AURHelper string | 40 | AURHelper string | ||
| 41 | Microcode bool | 41 | Microcode bool | ||
| 42 | InstallDankLinux bool | 42 | InstallDankLinux bool | ||
| 43 | + | EnableChaotic bool | |||
| 43 | } | 44 | } | ||
| 44 | 45 | ||||
| 45 | // FormModelImpl implements FormModel interface | 46 | // FormModelImpl implements FormModel interface | ||
| @@ -312,6 +313,7 @@ func (fm *FormModelImpl) extractData() { | |||||
| 312 | Locale: fm.fields[5].Value(), | 313 | Locale: fm.fields[5].Value(), | ||
| 313 | Keymap: fm.fields[6].Value(), | 314 | Keymap: fm.fields[6].Value(), | ||
| 314 | InstallDankLinux: fm.data.InstallDankLinux, | 315 | InstallDankLinux: fm.data.InstallDankLinux, | ||
| 316 | + | EnableChaotic: fm.data.EnableChaotic, | |||
| 315 | } | 317 | } | ||
| 316 | } | 318 | } | ||
| 317 | 319 | ||||
A internal/interfaces/tui/views/encryption.go +48 -0
| @@ -0,0 +1,48 @@ | |||
| 1 | + | package views | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "strings" | |
| 5 | + | ||
| 6 | + | "github.com/bnema/archup/internal/interfaces/tui/models" | |
| 7 | + | "github.com/charmbracelet/lipgloss" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | // RenderEncryptionSelection renders the encryption selection screen. | |
| 11 | + | func RenderEncryptionSelection(em *models.EncryptionModelImpl) string { | |
| 12 | + | var b strings.Builder | |
| 13 | + | ||
| 14 | + | title := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("12")) | |
| 15 | + | info := lipgloss.NewStyle().Foreground(lipgloss.Color("8")) | |
| 16 | + | active := lipgloss.NewStyle().Foreground(lipgloss.Color("10")).Bold(true) | |
| 17 | + | desc := lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Faint(true) | |
| 18 | + | ||
| 19 | + | b.WriteString("\n") | |
| 20 | + | b.WriteString(title.Render("Disk Encryption")) | |
| 21 | + | b.WriteString("\n\n") | |
| 22 | + | ||
| 23 | + | b.WriteString(info.Render("Choose how to encrypt your installation disk.")) | |
| 24 | + | b.WriteString("\n\n") | |
| 25 | + | ||
| 26 | + | for i, option := range em.Options() { | |
| 27 | + | prefix := " " | |
| 28 | + | style := lipgloss.NewStyle() | |
| 29 | + | labelStyle := lipgloss.NewStyle() | |
| 30 | + | ||
| 31 | + | if i == em.SelectedIndex() { | |
| 32 | + | prefix = "> " | |
| 33 | + | style = active | |
| 34 | + | labelStyle = active | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | b.WriteString(style.Render(prefix)) | |
| 38 | + | b.WriteString(labelStyle.Render(option.Label)) | |
| 39 | + | b.WriteString("\n") | |
| 40 | + | b.WriteString(desc.Render(" " + option.Description)) | |
| 41 | + | b.WriteString("\n") | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | b.WriteString("\n") | |
| 45 | + | b.WriteString(info.Render("↑/↓ to select • enter to confirm • esc to go back")) | |
| 46 | + | ||
| 47 | + | return b.String() | |
| 48 | + | } | |
| @@ -0,0 +1,48 @@ | |||||
| 1 | + | package views | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "strings" | |||
| 5 | + | ||||
| 6 | + | "github.com/bnema/archup/internal/interfaces/tui/models" | |||
| 7 | + | "github.com/charmbracelet/lipgloss" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | // RenderEncryptionSelection renders the encryption selection screen. | |||
| 11 | + | func RenderEncryptionSelection(em *models.EncryptionModelImpl) string { | |||
| 12 | + | var b strings.Builder | |||
| 13 | + | ||||
| 14 | + | title := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("12")) | |||
| 15 | + | info := lipgloss.NewStyle().Foreground(lipgloss.Color("8")) | |||
| 16 | + | active := lipgloss.NewStyle().Foreground(lipgloss.Color("10")).Bold(true) | |||
| 17 | + | desc := lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Faint(true) | |||
| 18 | + | ||||
| 19 | + | b.WriteString("\n") | |||
| 20 | + | b.WriteString(title.Render("Disk Encryption")) | |||
| 21 | + | b.WriteString("\n\n") | |||
| 22 | + | ||||
| 23 | + | b.WriteString(info.Render("Choose how to encrypt your installation disk.")) | |||
| 24 | + | b.WriteString("\n\n") | |||
| 25 | + | ||||
| 26 | + | for i, option := range em.Options() { | |||
| 27 | + | prefix := " " | |||
| 28 | + | style := lipgloss.NewStyle() | |||
| 29 | + | labelStyle := lipgloss.NewStyle() | |||
| 30 | + | ||||
| 31 | + | if i == em.SelectedIndex() { | |||
| 32 | + | prefix = "> " | |||
| 33 | + | style = active | |||
| 34 | + | labelStyle = active | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | b.WriteString(style.Render(prefix)) | |||
| 38 | + | b.WriteString(labelStyle.Render(option.Label)) | |||
| 39 | + | b.WriteString("\n") | |||
| 40 | + | b.WriteString(desc.Render(" " + option.Description)) | |||
| 41 | + | b.WriteString("\n") | |||
| 42 | + | } | |||
| 43 | + | ||||
| 44 | + | b.WriteString("\n") | |||
| 45 | + | b.WriteString(info.Render("↑/↓ to select • enter to confirm • esc to go back")) | |||
| 46 | + | ||||
| 47 | + | return b.String() | |||
| 48 | + | } | |||