CH-J SM Mobilegestione server su SSH
Menu
Sorgente pubblicata

CH-J SM Mobile

Sfoglia directory e file per una specifica release di applicazioni.

Scarica sorgente ZIP
CH-J / SM Mobile 134 file
sm-mobile-20261016T050549Z-3678f2a05fa9 Aggiunto: · Costruito:
File
gates.dart Aggiunto: Data non registrata 10,3 KB · 282 righe
Scarica file
1import 'package:flutter/material.dart';
2import '../core/app_controller.dart';
3import '../core/failure.dart';
4import '../l10n/strings.dart';
5import 'common.dart';
7Widget languagePicker(BuildContext context, AppController c) =>
8 DropdownButtonFormField<String>(
9 initialValue: c.locale?.languageCode ?? 'system',
10 decoration: InputDecoration(labelText: context.tr('language')),
11 items: ['system', 'cs', 'de', 'en']
12 .map(
13 (v) => DropdownMenuItem(
14 value: v,
15 child: Text(switch (v) {
16 'cs' => 'Čeština',
17 'de' => 'Deutsch',
18 'en' => 'English',
19 _ => context.tr('system'),
20 }),
21 ),
22 )
23 .toList(),
24 onChanged: (v) async {
25 c.locale = v == 'system' ? null : Locale(v!);
26 try {
27 await c.saveSettings();
28 } catch (e) {
29 if (context.mounted) showFailure(context, e);
30 }
31 },
32 );
34class Onboarding extends StatefulWidget {
35 final AppController controller;
36 const Onboarding(this.controller, {super.key});
37 @override
38 State<Onboarding> createState() => _OnboardingState();
41class _OnboardingState extends State<Onboarding> {
42 int step = 0;
43 bool consent = false, busy = false, declined = false;
44 @override
45 Widget build(BuildContext context) {
46 final c = widget.controller;
47 final lang = Localizations.localeOf(context).languageCode;
48 return Scaffold(
49 appBar: AppBar(title: const Text('CH-J · SM Mobile')),
50 body: SafeArea(
51 child: Center(
52 child: ConstrainedBox(
53 constraints: const BoxConstraints(maxWidth: 760),
54 child: Padding(
55 padding: const EdgeInsets.all(20),
56 child: LayoutBuilder(
57 builder: (context, constraints) => SingleChildScrollView(
58 child: SizedBox(
59 height: constraints.maxHeight.clamp(
60 600 * MediaQuery.textScalerOf(context).scale(1),
61 double.infinity,
62 ),
63 child: Column(
64 crossAxisAlignment: CrossAxisAlignment.stretch,
65 children: [
66 if (step == 0) ...[
67 Text(
68 context.tr('welcome'),
69 style: Theme.of(context).textTheme.headlineSmall,
70 ),
71 const SizedBox(height: 16),
72 languagePicker(context, c),
73 const SizedBox(height: 16),
74 Text(context.tr('localOnly')),
75 const SizedBox(height: 16),
76 ],
77 Text(
78 context.tr(step == 0 ? 'guidance' : 'license'),
79 style: Theme.of(context).textTheme.titleLarge,
80 ),
81 const SizedBox(height: 12),
82 Expanded(
83 child: Card(
84 child: SingleChildScrollView(
85 padding: const EdgeInsets.all(16),
86 child: SelectableText(
87 step == 0
88 ? c.legal.guidance[lang] ??
89 c.legal.guidance['en']!
90 : c.legal.license,
91 ),
92 ),
93 ),
94 ),
95 if (step == 1)
96 CheckboxListTile(
97 value: consent,
98 onChanged: busy
99 ? null
100 : (v) => setState(() => consent = v!),
101 title: Text(context.tr('consent')),
102 contentPadding: EdgeInsets.zero,
103 ),
104 if (declined)
105 Text(
106 context.tr('licenseDeclined'),
107 style: TextStyle(
108 color: Theme.of(context).colorScheme.error,
109 ),
110 ),
111 Wrap(
112 spacing: 12,
113 alignment: WrapAlignment.end,
114 children: [
115 if (step == 1)
116 TextButton(
117 onPressed: busy
118 ? null
119 : () => setState(() {
120 declined = true;
121 consent = false;
122 }),
123 child: Text(context.tr('decline')),
124 ),
125 FilledButton(
126 onPressed: busy || (step == 1 && !consent)
127 ? null
128 : () async {
129 if (step == 0) {
130 setState(() => step = 1);
131 return;
132 }
133 setState(() => busy = true);
134 try {
135 await c.acceptLicense();
136 } catch (e) {
137 if (context.mounted) {
138 showFailure(context, e);
139 }
140 } finally {
141 if (mounted) {
142 setState(() => busy = false);
143 }
144 }
145 },
146 child: Text(
147 context.tr(
148 step == 0 ? 'continueAction' : 'accept',
149 ),
150 ),
151 ),
152 ],
153 ),
154 ],
155 ),
156 ),
157 ),
158 ),
159 ),
160 ),
161 ),
162 ),
163 );
164 }
167class VaultGate extends StatefulWidget {
168 final AppController controller;
169 const VaultGate(this.controller, {super.key});
170 @override
171 State<VaultGate> createState() => _VaultGateState();
174class _VaultGateState extends State<VaultGate> {
175 final password = TextEditingController(), repeated = TextEditingController();
176 bool busy = false;
177 Future<void> open({bool biometric = false}) async {
178 setState(() => busy = true);
179 try {
180 final c = widget.controller;
181 if (biometric) {
182 await c.biometricUnlock(context.tr('biometricReason'));
183 } else {
184 if (!c.initialized && password.text != repeated.text) {
185 throw const Failure('passwordMismatch');
186 }
187 await c.openVault(password.text, create: !c.initialized);
188 }
189 password.clear();
190 repeated.clear();
191 } catch (e) {
192 if (mounted) showFailure(context, e);
193 } finally {
194 if (mounted) setState(() => busy = false);
195 }
196 }
198 @override
199 void dispose() {
200 password.dispose();
201 repeated.dispose();
202 super.dispose();
203 }
205 @override
206 Widget build(BuildContext context) => Scaffold(
207 appBar: AppBar(title: const Text('SM Mobile')),
208 body: SafeArea(
209 child: Center(
210 child: SingleChildScrollView(
211 padding: const EdgeInsets.all(24),
212 child: ConstrainedBox(
213 constraints: const BoxConstraints(maxWidth: 450),
214 child: Column(
215 crossAxisAlignment: CrossAxisAlignment.stretch,
216 children: [
217 Icon(
218 Icons.lock_outline,
219 size: 56,
220 color: Theme.of(context).colorScheme.primary,
221 ),
222 const SizedBox(height: 20),
223 Text(
224 context.tr(
225 widget.controller.initialized ? 'vault' : 'createVault',
226 ),
227 style: Theme.of(context).textTheme.headlineSmall,
228 textAlign: TextAlign.center,
229 ),
230 const SizedBox(height: 20),
231 if (!widget.controller.initialized) ...[
232 Text(context.tr('vaultAdvice')),
233 const SizedBox(height: 20),
234 ],
235 TextField(
236 controller: password,
237 obscureText: true,
238 enableSuggestions: false,
239 autocorrect: false,
240 decoration: InputDecoration(
241 labelText: context.tr('masterPassword'),
242 ),
243 onSubmitted: (_) {
244 if (!busy) open();
245 },
246 ),
247 if (!widget.controller.initialized) ...[
248 const SizedBox(height: 16),
249 TextField(
250 controller: repeated,
251 obscureText: true,
252 enableSuggestions: false,
253 autocorrect: false,
254 decoration: InputDecoration(
255 labelText: context.tr('confirmPassword'),
256 ),
257 ),
258 ],
259 const SizedBox(height: 20),
260 FilledButton(
261 onPressed: busy ? null : open,
262 child: Text(
263 context.tr(
264 widget.controller.initialized ? 'unlock' : 'createVault',
265 ),
266 ),
267 ),
268 if (widget.controller.initialized &&
269 widget.controller.biometric)
270 OutlinedButton(
271 onPressed: busy ? null : () => open(biometric: true),
272 child: Text(context.tr('unlockBiometric')),
273 ),
274 if (busy) const LinearProgressIndicator(),
275 ],
276 ),
277 ),
278 ),
279 ),
280 ),
281 );
SHA-25666f06d67bd62c622d0039667bfef1c85d46a17766dcf22da716031833a713edb

CH-J Proprietary Software License 1.14 · La sorgente è fornita sotto CH-J Proprietary Software License 1.14. La sua disponibilità non modifica i termini di licenza o concede diritti aggiuntivi.

Archivio SHA-256c486a807e4828998b9c57ab0f2e760f7980b31965fe118e89d3e1a5a2b65be65