1use ratatui::{
7 style::{Modifier, Style},
8 text::{Line, Span},
9};
10
11use crate::{i18n, state::AppState, theme::Theme};
12
13fn selected_aur_vote_state_text(app: &AppState) -> Option<String> {
24 let selected = app.results.get(app.selected)?;
25 if !matches!(selected.source, crate::state::Source::Aur) {
26 return None;
27 }
28 let pkgbase = &selected.name;
29 Some(match app.aur_vote_state_by_pkgbase.get(pkgbase) {
30 Some(crate::state::app_state::AurVoteStateUi::Loading) => "Loading...".to_string(),
31 Some(crate::state::app_state::AurVoteStateUi::Voted) => "Voted".to_string(),
32 Some(crate::state::app_state::AurVoteStateUi::NotVoted) => "Not voted".to_string(),
33 Some(crate::state::app_state::AurVoteStateUi::Error(message)) => {
34 format!("Error ({message})")
35 }
36 _ => return None,
37 })
38}
39
40pub fn format_details_lines(app: &AppState, _area_width: u16, th: &Theme) -> Vec<Line<'static>> {
54 fn kv(key: &str, val: String, th: &Theme) -> Line<'static> {
67 Line::from(vec![
68 Span::styled(
69 format!("{key}: "),
70 Style::default()
71 .fg(th.sapphire)
72 .add_modifier(Modifier::BOLD),
73 ),
74 Span::styled(val, Style::default().fg(th.text)),
75 ])
76 }
77 let d = &app.details;
78 let repo_display = if crate::index::is_manjaro_name_or_owner(&d.name, &d.owner) {
80 "manjaro".to_string()
81 } else {
82 d.repository.clone()
83 };
84 let mut lines = vec![
86 kv(
87 &i18n::t(app, "app.details.fields.repository"),
88 repo_display,
89 th,
90 ),
91 kv(
92 &i18n::t(app, "app.details.fields.package_name"),
93 d.name.clone(),
94 th,
95 ),
96 kv(
97 &i18n::t(app, "app.details.fields.version"),
98 d.version.clone(),
99 th,
100 ),
101 kv(
102 &i18n::t(app, "app.details.fields.description"),
103 d.description.clone(),
104 th,
105 ),
106 kv(
107 &i18n::t(app, "app.details.fields.architecture"),
108 d.architecture.clone(),
109 th,
110 ),
111 kv(&i18n::t(app, "app.details.fields.url"), d.url.clone(), th),
112 kv(
113 &i18n::t(app, "app.details.fields.licences"),
114 join(&d.licenses),
115 th,
116 ),
117 kv(
118 &i18n::t(app, "app.details.fields.provides"),
119 join(&d.provides),
120 th,
121 ),
122 kv(
123 &i18n::t(app, "app.details.fields.depends_on"),
124 join(&d.depends),
125 th,
126 ),
127 kv(
128 &i18n::t(app, "app.details.fields.optional_dependencies"),
129 join(&d.opt_depends),
130 th,
131 ),
132 kv(
133 &i18n::t(app, "app.details.fields.required_by"),
134 join(&d.required_by),
135 th,
136 ),
137 kv(
138 &i18n::t(app, "app.details.fields.optional_for"),
139 join(&d.optional_for),
140 th,
141 ),
142 kv(
143 &i18n::t(app, "app.details.fields.conflicts_with"),
144 join(&d.conflicts),
145 th,
146 ),
147 kv(
148 &i18n::t(app, "app.details.fields.replaces"),
149 join(&d.replaces),
150 th,
151 ),
152 kv(
153 &i18n::t(app, "app.details.fields.download_size"),
154 d.download_size.map_or_else(
155 || i18n::t(app, "app.details.fields.not_available"),
156 human_bytes,
157 ),
158 th,
159 ),
160 kv(
161 &i18n::t(app, "app.details.fields.install_size"),
162 d.install_size.map_or_else(
163 || i18n::t(app, "app.details.fields.not_available"),
164 human_bytes,
165 ),
166 th,
167 ),
168 kv(
169 &i18n::t(app, "app.details.fields.package_owner"),
170 d.owner.clone(),
171 th,
172 ),
173 kv(
174 &i18n::t(app, "app.details.fields.build_date"),
175 d.build_date.clone(),
176 th,
177 ),
178 ];
179 let pkgb_label = if app.pkgb_visible {
181 i18n::t(app, "app.details.hide_pkgbuild")
182 } else {
183 i18n::t(app, "app.details.show_pkgbuild")
184 };
185 lines.push(Line::from(vec![Span::styled(
186 pkgb_label,
187 Style::default()
188 .fg(th.mauve)
189 .add_modifier(Modifier::UNDERLINED | Modifier::BOLD),
190 )]));
191
192 if let Some(vote_state_text) = selected_aur_vote_state_text(app) {
194 lines.push(kv("AUR vote state", vote_state_text, th));
195 let comments_label = if app.comments_visible {
196 i18n::t(app, "app.details.hide_comments")
197 } else {
198 i18n::t(app, "app.details.show_comments")
199 };
200 lines.push(Line::from(vec![Span::styled(
201 comments_label,
202 Style::default()
203 .fg(th.mauve)
204 .add_modifier(Modifier::UNDERLINED | Modifier::BOLD),
205 )]));
206 }
207 lines
208}
209
210pub(crate) fn join(list: &[String]) -> String {
221 if list.is_empty() {
222 "-".into()
223 } else {
224 list.join(", ")
225 }
226}
227
228#[must_use]
239pub fn format_bytes(value: u64) -> String {
240 const UNITS: [&str; 6] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
241 #[allow(clippy::cast_precision_loss)]
242 let mut size = value as f64;
243 let mut unit_index = 0usize;
244 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
245 size /= 1024.0;
246 unit_index += 1;
247 }
248 if unit_index == 0 {
249 format!("{value} {}", UNITS[unit_index])
250 } else {
251 format!("{size:.1} {}", UNITS[unit_index])
252 }
253}
254
255#[must_use]
266pub fn format_signed_bytes(value: i64) -> String {
267 if value == 0 {
268 return "0 B".to_string();
269 }
270 let magnitude = value.unsigned_abs();
271 if value > 0 {
272 format!("+{}", format_bytes(magnitude))
273 } else {
274 format!("-{}", format_bytes(magnitude))
275 }
276}
277
278#[must_use]
290pub fn human_bytes(n: u64) -> String {
291 const UNITS: [&str; 6] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
292 #[allow(clippy::cast_precision_loss)]
293 let mut v = n as f64;
294 let mut i = 0;
295 while v >= 1024.0 && i < UNITS.len() - 1 {
296 v /= 1024.0;
297 i += 1;
298 }
299 format!("{v:.1} {}", UNITS[i])
300}