package tui import ( "context" "fmt" "strings" "github.com/AvengeMedia/DankMaterialShell/backend/internal/deps" "github.com/AvengeMedia/DankMaterialShell/backend/internal/distros" tea "github.com/charmbracelet/bubbletea" ) func (m Model) viewDetectingDeps() string { var b strings.Builder b.WriteString(m.renderBanner()) b.WriteString("\n") title := m.styles.Title.Render("Detecting Dependencies") b.WriteString(title) b.WriteString("\n\n") spinner := m.spinner.View() status := m.styles.Normal.Render("Scanning system for existing packages and configurations...") b.WriteString(fmt.Sprintf("%s %s", spinner, status)) return b.String() } func (m Model) viewDependencyReview() string { var b strings.Builder b.WriteString(m.renderBanner()) b.WriteString("\n") title := m.styles.Title.Render("Dependency Review") b.WriteString(title) b.WriteString("\n\n") if len(m.dependencies) > 0 { for i, dep := range m.dependencies { var status string var reinstallMarker string var variantMarker string isDMS := dep.Name == "dms (DankMaterialShell)" if dep.CanToggle && dep.Variant == deps.VariantGit { variantMarker = "[git] " } if m.disabledItems[dep.Name] { reinstallMarker = "✗ " status = m.styles.Subtle.Render("Will skip") } else if m.reinstallItems[dep.Name] { reinstallMarker = "🔄 " status = m.styles.Warning.Render("Will upgrade") } else if isDMS { reinstallMarker = "⚡ " switch dep.Status { case deps.StatusInstalled: status = m.styles.Success.Render("✓ Required (installed)") case deps.StatusMissing: status = m.styles.Warning.Render("○ Required (will install)") case deps.StatusNeedsUpdate: status = m.styles.Warning.Render("△ Required (needs update)") case deps.StatusNeedsReinstall: status = m.styles.Error.Render("! Required (needs reinstall)") } } else { switch dep.Status { case deps.StatusInstalled: status = m.styles.Subtle.Render("✓ Already installed") case deps.StatusMissing: status = m.styles.Warning.Render("○ Will install") case deps.StatusNeedsUpdate: status = m.styles.Warning.Render("△ Will install") case deps.StatusNeedsReinstall: status = m.styles.Error.Render("! Will install") } } var line string if i == m.selectedDep { line = fmt.Sprintf("▶ %s%s%-25s %s", reinstallMarker, variantMarker, dep.Name, status) if dep.Version != "" { line += fmt.Sprintf(" (%s)", dep.Version) } line = m.styles.SelectedOption.Render(line) } else { line = fmt.Sprintf(" %s%s%-25s %s", reinstallMarker, variantMarker, dep.Name, status) if dep.Version != "" { line += fmt.Sprintf(" (%s)", dep.Version) } line = m.styles.Normal.Render(line) } b.WriteString(line) b.WriteString("\n") } } b.WriteString("\n") help := m.styles.Subtle.Render("↑/↓: Navigate, Space: Toggle, G: Toggle stable/git, Enter: Continue") b.WriteString(help) return b.String() } func (m Model) updateDetectingDepsState(msg tea.Msg) (tea.Model, tea.Cmd) { if depsMsg, ok := msg.(depsDetectedMsg); ok { m.isLoading = false if depsMsg.err != nil { m.err = depsMsg.err m.state = StateError } else { m.dependencies = depsMsg.deps m.state = StateDependencyReview } return m, m.listenForLogs() } return m, m.listenForLogs() } func (m Model) updateDependencyReviewState(msg tea.Msg) (tea.Model, tea.Cmd) { if keyMsg, ok := msg.(tea.KeyMsg); ok { switch keyMsg.String() { case "up": if m.selectedDep > 0 { m.selectedDep-- } case "down": if m.selectedDep < len(m.dependencies)-1 { m.selectedDep++ } case " ": if len(m.dependencies) > 0 { depName := m.dependencies[m.selectedDep].Name isDMS := depName == "dms (DankMaterialShell)" if !isDMS { isInstalled := m.dependencies[m.selectedDep].Status == deps.StatusInstalled || m.dependencies[m.selectedDep].Status == deps.StatusNeedsReinstall if isInstalled { m.reinstallItems[depName] = !m.reinstallItems[depName] m.disabledItems[depName] = false } else { m.disabledItems[depName] = !m.disabledItems[depName] m.reinstallItems[depName] = false } } } case "g", "G": if len(m.dependencies) > 0 && m.dependencies[m.selectedDep].CanToggle { if m.dependencies[m.selectedDep].Variant == deps.VariantStable { m.dependencies[m.selectedDep].Variant = deps.VariantGit } else { m.dependencies[m.selectedDep].Variant = deps.VariantStable } } case "enter": // Check if on Gentoo - show USE flags screen if m.osInfo != nil { if config, exists := distros.Registry[m.osInfo.Distribution.ID]; exists && config.Family == distros.FamilyGentoo { m.state = StateGentooUseFlags return m, nil } } // Check if fingerprint is enabled if checkFingerprintEnabled() { m.state = StateAuthMethodChoice m.selectedConfig = 0 // Default to fingerprint return m, nil } else { m.state = StatePasswordPrompt m.passwordInput.Focus() return m, nil } case "esc": m.state = StateSelectWindowManager return m, nil } } return m, m.listenForLogs() } func (m Model) installPackages() tea.Cmd { return func() tea.Msg { if m.osInfo == nil { return packageInstallProgressMsg{ progress: 0.0, step: "Error: OS info not available", isComplete: true, } } installer, err := distros.NewPackageInstaller(m.osInfo.Distribution.ID, m.logChan) if err != nil { return packageInstallProgressMsg{ progress: 0.0, step: fmt.Sprintf("Error: %s", err.Error()), isComplete: true, } } // Convert TUI selection to deps enum var wm deps.WindowManager if m.selectedWM == 0 { wm = deps.WindowManagerNiri } else { wm = deps.WindowManagerHyprland } installerProgressChan := make(chan distros.InstallProgressMsg, 100) go func() { defer close(installerProgressChan) err := installer.InstallPackages(context.Background(), m.dependencies, wm, m.sudoPassword, m.reinstallItems, m.disabledItems, m.skipGentooUseFlags, installerProgressChan) if err != nil { installerProgressChan <- distros.InstallProgressMsg{ Progress: 0.0, Step: fmt.Sprintf("Installation error: %s", err.Error()), IsComplete: true, Error: err, } } }() // Convert installer messages to TUI messages go func() { for msg := range installerProgressChan { tuiMsg := packageInstallProgressMsg{ progress: msg.Progress, step: msg.Step, isComplete: msg.IsComplete, needsSudo: msg.NeedsSudo, commandInfo: msg.CommandInfo, logOutput: msg.LogOutput, error: msg.Error, } if msg.IsComplete { m.logChan <- fmt.Sprintf("[DEBUG] Sending completion signal: step=%s, progress=%.2f", msg.Step, msg.Progress) } m.packageProgressChan <- tuiMsg } m.logChan <- "[DEBUG] Installer channel closed" }() return packageInstallProgressMsg{ progress: 0.05, step: "Starting installation...", isComplete: false, } } }