Skip to main content

modde_ui/views/
settings.rs

1use std::path::PathBuf;
2
3use iced::widget::{button, column, pick_list, row, scrollable, text, text_input};
4use iced::{color, Alignment, Element, Length};
5
6use crate::app::{Message, NexusAuthStatus, SettingsState};
7
8/// Render the settings view.
9pub fn view(state: SettingsState) -> Element<'static, Message> {
10    let title = text("Settings").size(20);
11
12    // Nexus API key with validation status
13    let nexus_status_widget: Element<'static, Message> = match &state.nexus_status {
14        Some(NexusAuthStatus::Checking) => text("Checking...").size(12).color(color!(0xAAAA44)).into(),
15        Some(NexusAuthStatus::Valid { username, is_premium }) => {
16            let tier = if *is_premium { "Premium" } else { "Standard" };
17            text(format!("Logged in as {username} ({tier})"))
18                .size(12)
19                .color(color!(0x88CC88))
20                .into()
21        }
22        Some(NexusAuthStatus::Invalid(err)) => {
23            text(format!("Invalid: {err}"))
24                .size(12)
25                .color(color!(0xFF4444))
26                .into()
27        }
28        None => text("Not validated").size(12).into(),
29    };
30
31    let game_path_str = state.game_path.as_ref().map(|p| p.display().to_string()).unwrap_or_default();
32    let download_dir_str = state.download_dir.as_ref().map(|p| p.display().to_string()).unwrap_or_default();
33
34    let api_key_section = column![
35        text("Nexus Mods API Key").size(14),
36        text("Required for downloading mods and browsing collections.").size(11),
37        row![
38            text_input("Enter your API key...", &state.nexus_api_key)
39                .on_input(Message::SetNexusApiKey)
40                .padding(8)
41                .width(Length::Fill),
42            button(text("Validate").size(13))
43                .on_press(Message::ValidateNexusKey)
44                .style(button::primary)
45                .padding([6, 12]),
46        ]
47        .spacing(8)
48        .align_y(Alignment::Center),
49        nexus_status_widget,
50    ]
51    .spacing(4);
52
53    // Game install path
54    let game_path_section = column![
55        text("Game Install Path").size(14),
56        text("Root directory of the game installation.").size(11),
57        row![
58            text_input(
59                "/path/to/game",
60                &game_path_str,
61            )
62            .on_input(|s| Message::SetGamePath {
63                game_id: "default".to_string(),
64                path: PathBuf::from(s),
65            })
66            .padding(8)
67            .width(Length::Fill),
68            button(text("Browse").size(13))
69                .on_press(Message::BrowseGamePath)
70                .style(button::secondary)
71                .padding([6, 12]),
72        ]
73        .spacing(8)
74        .align_y(Alignment::Center),
75    ]
76    .spacing(4);
77
78    // Download directory
79    let download_dir_section = column![
80        text("Download Directory").size(14),
81        text("Where downloaded mod archives are stored.").size(11),
82        row![
83            text_input(
84                "/path/to/downloads",
85                &download_dir_str,
86            )
87            .on_input(|s| Message::SetDownloadDir(PathBuf::from(s)))
88            .padding(8)
89            .width(Length::Fill),
90            button(text("Browse").size(13))
91                .on_press(Message::BrowseDownloadDir)
92                .style(button::secondary)
93                .padding([6, 12]),
94        ]
95        .spacing(8)
96        .align_y(Alignment::Center),
97    ]
98    .spacing(4);
99
100    // Stock game snapshot with verify button
101    let stock_game_section = column![
102        text("Stock Game Snapshot").size(14),
103        text("Create a snapshot of your clean game install for virtual deployment.").size(11),
104        row![
105            button(text("Create Snapshot").size(13))
106                .on_press(Message::CreateStockSnapshot)
107                .style(button::primary)
108                .padding([6, 14]),
109            button(text("Verify Snapshot").size(13))
110                .on_press(Message::VerifyStockSnapshot)
111                .style(button::secondary)
112                .padding([6, 14]),
113            text(if state.has_stock_snapshot {
114                "Snapshot exists"
115            } else {
116                "No snapshot created"
117            })
118            .size(12),
119        ]
120        .spacing(12)
121        .align_y(Alignment::Center),
122    ]
123    .spacing(4);
124
125    // Theme selection
126    let theme_options = vec![
127        "Dark".to_string(),
128        "Light".to_string(),
129        "Dracula".to_string(),
130        "Nord".to_string(),
131        "Gruvbox Dark".to_string(),
132        "Catppuccin Mocha".to_string(),
133    ];
134    let theme_section = column![
135        text("Theme").size(14),
136        pick_list(
137            theme_options,
138            Some(state.theme_name.clone()),
139            Message::SetTheme,
140        )
141        .width(Length::Fixed(200.0)),
142    ]
143    .spacing(4);
144
145    let content = scrollable(
146        column![
147            api_key_section,
148            iced::widget::rule::horizontal(1),
149            game_path_section,
150            iced::widget::rule::horizontal(1),
151            download_dir_section,
152            iced::widget::rule::horizontal(1),
153            stock_game_section,
154            iced::widget::rule::horizontal(1),
155            theme_section,
156        ]
157        .spacing(16)
158        .padding(16),
159    )
160    .height(Length::Fill);
161
162    column![title, iced::widget::rule::horizontal(1), content,]
163        .spacing(8)
164        .padding(16)
165        .width(Length::Fill)
166        .height(Length::Fill)
167        .into()
168}