init_page.dart 9.8 KB

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