dioxus_ui_system/molecules/
qr_code.rs1use crate::styles::Style;
6use crate::theme::{use_style, use_theme};
7use dioxus::prelude::*;
8
9#[derive(Default, Clone, PartialEq, Debug)]
11pub enum QrCodeLevel {
12 Low,
14 #[default]
16 Medium,
17 Quartile,
19 High,
21}
22
23impl QrCodeLevel {
24 fn as_str(&self) -> &'static str {
25 match self {
26 QrCodeLevel::Low => "L",
27 QrCodeLevel::Medium => "M",
28 QrCodeLevel::Quartile => "Q",
29 QrCodeLevel::High => "H",
30 }
31 }
32}
33
34#[derive(Props, Clone, PartialEq)]
36pub struct QrCodeProps {
37 pub value: String,
39 #[props(default = 200)]
41 pub size: u32,
42 #[props(default)]
44 pub level: QrCodeLevel,
45 #[props(default)]
47 pub fg_color: Option<String>,
48 #[props(default)]
50 pub bg_color: Option<String>,
51 #[props(default = true)]
53 pub include_margin: bool,
54 #[props(default)]
56 pub style: Option<String>,
57 #[props(default)]
59 pub class: Option<String>,
60 #[props(default)]
62 pub title: Option<String>,
63}
64
65#[component]
84pub fn QrCode(props: QrCodeProps) -> Element {
85 let _theme = use_theme();
86
87 let container_style = use_style(|_t| Style::new().inline_block().build());
88
89 let fg = props
90 .fg_color
91 .clone()
92 .unwrap_or_else(|| "000000".to_string());
93 let bg = props
94 .bg_color
95 .clone()
96 .unwrap_or_else(|| "FFFFFF".to_string());
97
98 let api_url = format!(
101 "https://api.qrserver.com/v1/create-qr-code/?size={}x{}&data={}&ecc={}&color={}&bgcolor={}&margin={}",
102 props.size,
103 props.size,
104 urlencode(&props.value),
105 props.level.as_str(),
106 fg,
107 bg,
108 if props.include_margin { 4 } else { 0 }
109 );
110
111 let alt_text = props
112 .title
113 .clone()
114 .unwrap_or_else(|| format!("QR Code: {}", props.value));
115
116 rsx! {
117 div {
118 style: "{container_style} {props.style.clone().unwrap_or_default()}",
119 class: "{props.class.clone().unwrap_or_default()}",
120
121 img {
122 src: "{api_url}",
123 width: "{props.size}",
124 height: "{props.size}",
125 alt: "{alt_text}",
126 style: "display: block; max-width: 100%; height: auto;",
127 }
128 }
129 }
130}
131
132#[derive(Props, Clone, PartialEq)]
135pub struct QrCodeSvgProps {
136 pub value: String,
137 #[props(default = 200)]
138 pub size: u32,
139 #[props(default)]
140 pub level: QrCodeLevel,
141 #[props(default)]
142 pub style: Option<String>,
143 #[props(default)]
144 pub class: Option<String>,
145}
146
147#[component]
151pub fn QrCodeSvg(props: QrCodeSvgProps) -> Element {
152 let _theme = use_theme();
153 let module_count = 21; let _module_size = props.size / module_count as u32;
155
156 let pattern = generate_placeholder_pattern(&props.value, module_count);
159
160 let container_style = use_style(|_t| Style::new().inline_block().build());
161
162 rsx! {
163 div {
164 style: "{container_style} {props.style.clone().unwrap_or_default()}",
165 class: "{props.class.clone().unwrap_or_default()}",
166
167 svg {
168 width: "{props.size}",
169 height: "{props.size}",
170 view_box: "0 0 {module_count} {module_count}",
171 xmlns: "http://www.w3.org/2000/svg",
172
173 rect {
175 x: "0",
176 y: "0",
177 width: "{module_count}",
178 height: "{module_count}",
179 fill: "white",
180 }
181
182 for (row_idx, row) in pattern.iter().enumerate() {
184 for (col_idx, &is_dark) in row.iter().enumerate() {
185 if is_dark {
186 rect {
187 key: "{row_idx}-{col_idx}",
188 x: "{col_idx}",
189 y: "{row_idx}",
190 width: "1",
191 height: "1",
192 fill: "black",
193 }
194 }
195 }
196 }
197
198 PositionPattern { x: 0, y: 0 }
201 PositionPattern { x: module_count - 7, y: 0 }
203 PositionPattern { x: 0, y: module_count - 7 }
205 }
206 }
207 }
208}
209
210#[derive(Props, Clone, PartialEq)]
211struct PositionPatternProps {
212 x: usize,
213 y: usize,
214}
215
216#[component]
217fn PositionPattern(props: PositionPatternProps) -> Element {
218 let x = props.x;
219 let y = props.y;
220
221 rsx! {
222 rect { x: "{x}", y: "{y}", width: "7", height: "7", fill: "black" }
224 rect { x: "{x + 1}", y: "{y + 1}", width: "5", height: "5", fill: "white" }
226 rect { x: "{x + 2}", y: "{y + 2}", width: "3", height: "3", fill: "black" }
228 }
229}
230
231fn generate_placeholder_pattern(value: &str, size: usize) -> Vec<Vec<bool>> {
234 use std::collections::hash_map::DefaultHasher;
235 use std::hash::{Hash, Hasher};
236
237 let mut hasher = DefaultHasher::new();
238 value.hash(&mut hasher);
239 let hash = hasher.finish();
240
241 let mut pattern = vec![vec![false; size]; size];
242
243 for i in 0..size {
245 for j in 0..size {
246 if (i < 7 && j < 7) || (i < 7 && j >= size - 7) || (i >= size - 7 && j < 7) {
248 continue;
249 }
250 if i == 6 || j == 6 {
252 pattern[i][j] = (i + j) % 2 == 0;
253 continue;
254 }
255 let idx = i * size + j;
257 pattern[i][j] = ((hash >> (idx % 64)) & 1) == 1;
258 }
259 }
260
261 pattern
262}
263
264fn urlencode(s: &str) -> String {
265 s.chars()
266 .map(|c| match c {
267 'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.' | '~' => c.to_string(),
268 c => format!("%{:02X}", c as u8),
269 })
270 .collect()
271}