1use ratatui::{
7 style::{Modifier, Style},
8 text::{Line, Span},
9};
10
11use crate::{i18n, state::AppState, theme::Theme};
12
13pub fn format_details_lines(app: &AppState, _area_width: u16, th: &Theme) -> Vec<Line<'static>> {
27 fn kv(key: &str, val: String, th: &Theme) -> Line<'static> {
40 Line::from(vec![
41 Span::styled(
42 format!("{key}: "),
43 Style::default()
44 .fg(th.sapphire)
45 .add_modifier(Modifier::BOLD),
46 ),
47 Span::styled(val, Style::default().fg(th.text)),
48 ])
49 }
50 let d = &app.details;
51 let repo_display = if crate::index::is_manjaro_name_or_owner(&d.name, &d.owner) {
53 "manjaro".to_string()
54 } else {
55 d.repository.clone()
56 };
57 let mut lines = vec![
59 kv(
60 &i18n::t(app, "app.details.fields.repository"),
61 repo_display,
62 th,
63 ),
64 kv(
65 &i18n::t(app, "app.details.fields.package_name"),
66 d.name.clone(),
67 th,
68 ),
69 kv(
70 &i18n::t(app, "app.details.fields.version"),
71 d.version.clone(),
72 th,
73 ),
74 kv(
75 &i18n::t(app, "app.details.fields.description"),
76 d.description.clone(),
77 th,
78 ),
79 kv(
80 &i18n::t(app, "app.details.fields.architecture"),
81 d.architecture.clone(),
82 th,
83 ),
84 kv(&i18n::t(app, "app.details.fields.url"), d.url.clone(), th),
85 kv(
86 &i18n::t(app, "app.details.fields.licences"),
87 join(&d.licenses),
88 th,
89 ),
90 kv(
91 &i18n::t(app, "app.details.fields.provides"),
92 join(&d.provides),
93 th,
94 ),
95 kv(
96 &i18n::t(app, "app.details.fields.depends_on"),
97 join(&d.depends),
98 th,
99 ),
100 kv(
101 &i18n::t(app, "app.details.fields.optional_dependencies"),
102 join(&d.opt_depends),
103 th,
104 ),
105 kv(
106 &i18n::t(app, "app.details.fields.required_by"),
107 join(&d.required_by),
108 th,
109 ),
110 kv(
111 &i18n::t(app, "app.details.fields.optional_for"),
112 join(&d.optional_for),
113 th,
114 ),
115 kv(
116 &i18n::t(app, "app.details.fields.conflicts_with"),
117 join(&d.conflicts),
118 th,
119 ),
120 kv(
121 &i18n::t(app, "app.details.fields.replaces"),
122 join(&d.replaces),
123 th,
124 ),
125 kv(
126 &i18n::t(app, "app.details.fields.download_size"),
127 d.download_size.map_or_else(
128 || i18n::t(app, "app.details.fields.not_available"),
129 human_bytes,
130 ),
131 th,
132 ),
133 kv(
134 &i18n::t(app, "app.details.fields.install_size"),
135 d.install_size.map_or_else(
136 || i18n::t(app, "app.details.fields.not_available"),
137 human_bytes,
138 ),
139 th,
140 ),
141 kv(
142 &i18n::t(app, "app.details.fields.package_owner"),
143 d.owner.clone(),
144 th,
145 ),
146 kv(
147 &i18n::t(app, "app.details.fields.build_date"),
148 d.build_date.clone(),
149 th,
150 ),
151 ];
152 let pkgb_label = if app.pkgb_visible {
154 i18n::t(app, "app.details.hide_pkgbuild")
155 } else {
156 i18n::t(app, "app.details.show_pkgbuild")
157 };
158 lines.push(Line::from(vec![Span::styled(
159 pkgb_label,
160 Style::default()
161 .fg(th.mauve)
162 .add_modifier(Modifier::UNDERLINED | Modifier::BOLD),
163 )]));
164
165 let is_aur = app
167 .results
168 .get(app.selected)
169 .is_some_and(|item| matches!(item.source, crate::state::Source::Aur));
170 if is_aur {
171 let comments_label = if app.comments_visible {
172 i18n::t(app, "app.details.hide_comments")
173 } else {
174 i18n::t(app, "app.details.show_comments")
175 };
176 lines.push(Line::from(vec![Span::styled(
177 comments_label,
178 Style::default()
179 .fg(th.mauve)
180 .add_modifier(Modifier::UNDERLINED | Modifier::BOLD),
181 )]));
182 }
183 lines
184}
185
186pub(crate) fn join(list: &[String]) -> String {
197 if list.is_empty() {
198 "-".into()
199 } else {
200 list.join(", ")
201 }
202}
203
204#[must_use]
215pub fn format_bytes(value: u64) -> String {
216 const UNITS: [&str; 6] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
217 #[allow(clippy::cast_precision_loss)]
218 let mut size = value as f64;
219 let mut unit_index = 0usize;
220 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
221 size /= 1024.0;
222 unit_index += 1;
223 }
224 if unit_index == 0 {
225 format!("{value} {}", UNITS[unit_index])
226 } else {
227 format!("{size:.1} {}", UNITS[unit_index])
228 }
229}
230
231#[must_use]
242pub fn format_signed_bytes(value: i64) -> String {
243 if value == 0 {
244 return "0 B".to_string();
245 }
246 let magnitude = value.unsigned_abs();
247 if value > 0 {
248 format!("+{}", format_bytes(magnitude))
249 } else {
250 format!("-{}", format_bytes(magnitude))
251 }
252}
253
254#[must_use]
266pub fn human_bytes(n: u64) -> String {
267 const UNITS: [&str; 6] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
268 #[allow(clippy::cast_precision_loss)]
269 let mut v = n as f64;
270 let mut i = 0;
271 while v >= 1024.0 && i < UNITS.len() - 1 {
272 v /= 1024.0;
273 i += 1;
274 }
275 format!("{v:.1} {}", UNITS[i])
276}