init_page.dart 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  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) => v == null || v.isEmpty ? 'Campo obbligatorio' : null,
  157. ),
  158. const SizedBox(height: 16),
  159. _buildPasswordField(cs),
  160. const SizedBox(height: 16),
  161. _buildLabeledField(
  162. label: 'Ragione Sociale (opzionale)',
  163. icon: Icons.person_outline,
  164. controller: _ragsocCtrl,
  165. hintText: 'Inserisci la ragione sociale',
  166. ),
  167. const SizedBox(height: 16),
  168. _buildLabeledField(
  169. label: 'URL Backend',
  170. icon: Icons.link,
  171. controller: _urlCtrl,
  172. hintText: 'https://api.powerage.cloud',
  173. validator: (v) => v == null || v.isEmpty ? 'Campo obbligatorio' : null,
  174. ),
  175. const SizedBox(height: 24),
  176. FilledButton.icon(
  177. onPressed: authState.isLoading
  178. ? null
  179. : () {
  180. if (_formKey.currentState!.validate()) {
  181. ref.read(authProvider.notifier).initialize(
  182. idAgente: 0,
  183. codAgente: _codiceCtrl.text,
  184. password: _passwordCtrl.text,
  185. ragioneSociale: _ragsocCtrl.text.isEmpty ? null : _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. ),
  204. if (authState.error != null) ...[
  205. const SizedBox(height: 16),
  206. _buildErrorCard(authState.error!, cs),
  207. ],
  208. ],
  209. ),
  210. ),
  211. ),
  212. );
  213. }
  214. Widget _buildLabeledField({
  215. required String label,
  216. required IconData icon,
  217. required TextEditingController controller,
  218. required String hintText,
  219. String? Function(String?)? validator,
  220. }) {
  221. final cs = Theme.of(context).colorScheme;
  222. return Column(
  223. crossAxisAlignment: CrossAxisAlignment.start,
  224. children: [
  225. Text(
  226. label,
  227. style: TextStyle(
  228. fontSize: 12,
  229. height: 16 / 12,
  230. fontWeight: FontWeight.w600,
  231. color: cs.onSurface,
  232. ),
  233. ),
  234. const SizedBox(height: 8),
  235. TextFormField(
  236. controller: controller,
  237. decoration: InputDecoration(
  238. hintText: hintText,
  239. prefixIcon: Icon(icon, size: 20),
  240. ),
  241. validator: validator,
  242. ),
  243. ],
  244. );
  245. }
  246. Widget _buildPasswordField(ColorScheme cs) {
  247. return Column(
  248. crossAxisAlignment: CrossAxisAlignment.start,
  249. children: [
  250. Text(
  251. 'Password',
  252. style: TextStyle(
  253. fontSize: 12,
  254. height: 16 / 12,
  255. fontWeight: FontWeight.w600,
  256. color: cs.onSurface,
  257. ),
  258. ),
  259. const SizedBox(height: 8),
  260. TextFormField(
  261. controller: _passwordCtrl,
  262. obscureText: _obscurePassword,
  263. decoration: InputDecoration(
  264. hintText: '',
  265. prefixIcon: const Icon(Icons.lock_outline, size: 20),
  266. suffixIcon: IconButton(
  267. icon: Icon(
  268. _obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined,
  269. size: 20,
  270. ),
  271. onPressed: () {
  272. setState(() {
  273. _obscurePassword = !_obscurePassword;
  274. });
  275. },
  276. ),
  277. ),
  278. // validator: (v) =>
  279. // v == null || v.isEmpty ? 'Campo obbligatorio' : null,
  280. ),
  281. ],
  282. );
  283. }
  284. Widget _buildErrorCard(String error, ColorScheme cs) {
  285. return Container(
  286. padding: const EdgeInsets.all(12),
  287. decoration: BoxDecoration(
  288. color: cs.errorContainer,
  289. borderRadius: BorderRadius.circular(8),
  290. ),
  291. child: Row(
  292. children: [
  293. Icon(Icons.error_outline, color: cs.error, size: 20),
  294. const SizedBox(width: 8),
  295. Expanded(
  296. child: Text(
  297. error,
  298. style: TextStyle(color: cs.onErrorContainer, fontSize: 14),
  299. ),
  300. ),
  301. ],
  302. ),
  303. );
  304. }
  305. }