init_page.dart 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import '../../../providers/auth_provider.dart';
  4. import '../../../providers/database_provider.dart';
  5. class InitPage extends ConsumerStatefulWidget {
  6. const InitPage({super.key});
  7. @override
  8. ConsumerState<InitPage> createState() => _InitPageState();
  9. }
  10. class _InitPageState extends ConsumerState<InitPage> {
  11. final _codiceCtrl = TextEditingController();
  12. final _passwordCtrl = TextEditingController();
  13. final _ragsocCtrl = TextEditingController();
  14. final _urlCtrl = TextEditingController();
  15. final _formKey = GlobalKey<FormState>();
  16. bool _obscurePassword = true;
  17. @override
  18. void initState() {
  19. super.initState();
  20. _loadUrl();
  21. }
  22. Future<void> _loadUrl() async {
  23. final url = await ref.read(parametriDaoProvider).read('URL');
  24. _urlCtrl.text = url ?? 'https://';
  25. }
  26. @override
  27. void dispose() {
  28. _codiceCtrl.dispose();
  29. _passwordCtrl.dispose();
  30. _ragsocCtrl.dispose();
  31. _urlCtrl.dispose();
  32. super.dispose();
  33. }
  34. @override
  35. Widget build(BuildContext context) {
  36. final authState = ref.watch(authProvider);
  37. final theme = Theme.of(context);
  38. final cs = theme.colorScheme;
  39. return Scaffold(
  40. body: Stack(
  41. children: [
  42. Positioned(
  43. top: -MediaQuery.of(context).size.height * 0.08,
  44. right: -MediaQuery.of(context).size.width * 0.05,
  45. child: Container(
  46. width: 320,
  47. height: 320,
  48. decoration: BoxDecoration(
  49. color: cs.secondaryContainer.withValues(alpha: 0.15),
  50. shape: BoxShape.circle,
  51. ),
  52. ),
  53. ),
  54. Positioned(
  55. bottom: -MediaQuery.of(context).size.height * 0.08,
  56. left: -MediaQuery.of(context).size.width * 0.05,
  57. child: Container(
  58. width: 320,
  59. height: 320,
  60. decoration: BoxDecoration(
  61. color: cs.primaryContainer.withValues(alpha: 0.08),
  62. shape: BoxShape.circle,
  63. ),
  64. ),
  65. ),
  66. Center(
  67. child: SingleChildScrollView(
  68. padding: const EdgeInsets.symmetric(horizontal: 16),
  69. child: ConstrainedBox(
  70. constraints: const BoxConstraints(maxWidth: 400),
  71. child: Column(
  72. mainAxisSize: MainAxisSize.min,
  73. children: [
  74. const SizedBox(height: 32),
  75. _buildBrandHeader(theme, cs),
  76. const SizedBox(height: 32),
  77. _buildFormCard(authState, theme, cs),
  78. const SizedBox(height: 32),
  79. const SizedBox(height: 24),
  80. ],
  81. ),
  82. ),
  83. ),
  84. ),
  85. ],
  86. ),
  87. );
  88. }
  89. Widget _buildBrandHeader(ThemeData theme, ColorScheme cs) {
  90. return Column(
  91. children: [
  92. Container(
  93. width: 64,
  94. height: 64,
  95. decoration: BoxDecoration(
  96. color: cs.primary,
  97. borderRadius: BorderRadius.circular(12),
  98. boxShadow: [
  99. BoxShadow(
  100. color: Colors.black.withValues(alpha: 0.15),
  101. blurRadius: 12,
  102. offset: const Offset(0, 4),
  103. ),
  104. ],
  105. ),
  106. child: const Icon(Icons.store, color: Colors.white, size: 40),
  107. ),
  108. const SizedBox(height: 16),
  109. Text(
  110. 'Benvenuto in PowerAge',
  111. style: theme.textTheme.headlineLarge?.copyWith(
  112. color: cs.primary,
  113. letterSpacing: -0.02,
  114. ),
  115. ),
  116. const SizedBox(height: 8),
  117. Text(
  118. 'Configura il tuo account agente per iniziare',
  119. textAlign: TextAlign.center,
  120. style: theme.textTheme.bodyMedium?.copyWith(
  121. color: cs.onSurfaceVariant,
  122. ),
  123. ),
  124. ],
  125. );
  126. }
  127. Widget _buildFormCard(AuthState authState, ThemeData theme, ColorScheme cs) {
  128. return Container(
  129. decoration: BoxDecoration(
  130. color: Colors.white,
  131. borderRadius: BorderRadius.circular(12),
  132. border: Border.all(color: cs.outlineVariant),
  133. boxShadow: [
  134. BoxShadow(
  135. color: const Color(0xFF0F172A).withValues(alpha: 0.05),
  136. blurRadius: 12,
  137. offset: const Offset(0, 4),
  138. ),
  139. ],
  140. ),
  141. child: Padding(
  142. padding: const EdgeInsets.all(24),
  143. child: Form(
  144. key: _formKey,
  145. child: Column(
  146. crossAxisAlignment: CrossAxisAlignment.stretch,
  147. children: [
  148. _buildLabeledField(
  149. label: 'Codice Agente',
  150. icon: Icons.badge_outlined,
  151. controller: _codiceCtrl,
  152. hintText: 'Inserisci il codice agente',
  153. validator: (v) =>
  154. v == null || v.isEmpty ? 'Campo obbligatorio' : null,
  155. ),
  156. const SizedBox(height: 16),
  157. _buildPasswordField(cs),
  158. const SizedBox(height: 16),
  159. _buildLabeledField(
  160. label: 'Ragione Sociale (opzionale)',
  161. icon: Icons.person_outline,
  162. controller: _ragsocCtrl,
  163. hintText: 'Inserisci la ragione sociale',
  164. ),
  165. const SizedBox(height: 16),
  166. _buildLabeledField(
  167. label: 'URL Backend',
  168. icon: Icons.link,
  169. controller: _urlCtrl,
  170. hintText: 'https://api.powerage.cloud',
  171. validator: (v) =>
  172. v == null || v.isEmpty ? 'Campo obbligatorio' : null,
  173. ),
  174. const SizedBox(height: 24),
  175. FilledButton.icon(
  176. onPressed: authState.isLoading
  177. ? null
  178. : () {
  179. if (_formKey.currentState!.validate()) {
  180. ref.read(authProvider.notifier).initialize(
  181. codAgente: _codiceCtrl.text,
  182. password: _passwordCtrl.text,
  183. ragioneSociale: _ragsocCtrl.text.isEmpty
  184. ? null
  185. : _ragsocCtrl.text,
  186. url: _urlCtrl.text,
  187. );
  188. }
  189. },
  190. icon: authState.isLoading
  191. ? const SizedBox(
  192. width: 20,
  193. height: 20,
  194. child: CircularProgressIndicator(
  195. strokeWidth: 2,
  196. color: Colors.white,
  197. ),
  198. )
  199. : const Icon(Icons.arrow_forward),
  200. label: Text(
  201. authState.isLoading ? 'Configurazione...' : 'Configura'),
  202. ),
  203. if (authState.error != null) ...[
  204. const SizedBox(height: 16),
  205. _buildErrorCard(authState.error!, cs),
  206. ],
  207. ],
  208. ),
  209. ),
  210. ),
  211. );
  212. }
  213. Widget _buildLabeledField({
  214. required String label,
  215. required IconData icon,
  216. required TextEditingController controller,
  217. required String hintText,
  218. String? Function(String?)? validator,
  219. }) {
  220. final cs = Theme.of(context).colorScheme;
  221. return Column(
  222. crossAxisAlignment: CrossAxisAlignment.start,
  223. children: [
  224. Text(
  225. label,
  226. style: TextStyle(
  227. fontSize: 12,
  228. height: 16 / 12,
  229. fontWeight: FontWeight.w600,
  230. color: cs.onSurface,
  231. ),
  232. ),
  233. const SizedBox(height: 8),
  234. TextFormField(
  235. controller: controller,
  236. decoration: InputDecoration(
  237. hintText: hintText,
  238. prefixIcon: Icon(icon, size: 20),
  239. ),
  240. validator: validator,
  241. ),
  242. ],
  243. );
  244. }
  245. Widget _buildPasswordField(ColorScheme cs) {
  246. return Column(
  247. crossAxisAlignment: CrossAxisAlignment.start,
  248. children: [
  249. Text(
  250. 'Password',
  251. style: TextStyle(
  252. fontSize: 12,
  253. height: 16 / 12,
  254. fontWeight: FontWeight.w600,
  255. color: cs.onSurface,
  256. ),
  257. ),
  258. const SizedBox(height: 8),
  259. TextFormField(
  260. controller: _passwordCtrl,
  261. obscureText: _obscurePassword,
  262. decoration: InputDecoration(
  263. hintText: '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022',
  264. prefixIcon: const Icon(Icons.lock_outline, size: 20),
  265. suffixIcon: IconButton(
  266. icon: Icon(
  267. _obscurePassword
  268. ? Icons.visibility_outlined
  269. : Icons.visibility_off_outlined,
  270. size: 20,
  271. ),
  272. onPressed: () {
  273. setState(() {
  274. _obscurePassword = !_obscurePassword;
  275. });
  276. },
  277. ),
  278. ),
  279. validator: (v) =>
  280. v == null || v.isEmpty ? 'Campo obbligatorio' : null,
  281. ),
  282. ],
  283. );
  284. }
  285. Widget _buildErrorCard(String error, ColorScheme cs) {
  286. return Container(
  287. padding: const EdgeInsets.all(12),
  288. decoration: BoxDecoration(
  289. color: cs.errorContainer,
  290. borderRadius: BorderRadius.circular(8),
  291. ),
  292. child: Row(
  293. children: [
  294. Icon(Icons.error_outline, color: cs.error, size: 20),
  295. const SizedBox(width: 8),
  296. Expanded(
  297. child: Text(
  298. error,
  299. style: TextStyle(color: cs.onErrorContainer, fontSize: 14),
  300. ),
  301. ),
  302. ],
  303. ),
  304. );
  305. }
  306. }