import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:manager_app/l10n/app_localizations.dart'; import 'package:manager_app/Models/managerContext.dart'; import 'package:manager_app/app_context.dart'; import 'package:manager_app/Components/common_loader.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_api_new/api.dart'; import 'package:provider/provider.dart'; class ApiKeysScreen extends StatefulWidget { const ApiKeysScreen({Key? key}) : super(key: key); @override _ApiKeysScreenState createState() => _ApiKeysScreenState(); } class _ApiKeysScreenState extends State { List> _keys = []; bool _loading = true; static String _appTypeName(dynamic v) { if (v is String) return v; switch (v as int?) { case 0: return 'VisitApp'; case 1: return 'TabletApp'; default: return 'Other'; } } Future _loadKeys(ManagerAppContext ctx) async { try { final response = await ctx.clientAPI!.apiKeyApi!.apiKeyGetApiKeysWithHttpInfo(); if (response.statusCode == 200) { final List json = jsonDecode(utf8.decode(response.bodyBytes)); setState(() { _keys = json.cast>(); _loading = false; }); } } catch (e) { setState(() => _loading = false); } } Future _createKey(ManagerAppContext ctx, String name, ApiKeyAppType appType, DateTime? dateExpiration) async { final response = await ctx.clientAPI!.apiKeyApi!.apiKeyCreateApiKeyWithHttpInfo( CreateApiKeyRequest(name: name, appType: appType, dateExpiration: dateExpiration), ); if (response.statusCode == 200 || response.statusCode == 201) { final Map json = jsonDecode(utf8.decode(response.bodyBytes)); await _loadKeys(ctx); return json['key'] as String?; } return null; } Future _revokeKey(ManagerAppContext ctx, String id) async { await ctx.clientAPI!.apiKeyApi!.apiKeyRevokeApiKey(id); await _loadKeys(ctx); } void _showCreateDialog(BuildContext context, ManagerAppContext ctx) { final l = AppLocalizations.of(context)!; final nameCtrl = TextEditingController(); ApiKeyAppType selectedType = ApiKeyAppType.number0; DateTime? selectedExpiration; showDialog( context: context, builder: (_) => StatefulBuilder(builder: (ctx2, setLocal) { return AlertDialog( title: Text(l.createApiKey), content: Column(mainAxisSize: MainAxisSize.min, children: [ TextField(controller: nameCtrl, decoration: InputDecoration(labelText: l.name)), const SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(l.appType, style: const TextStyle(fontSize: 12, color: Colors.grey)), DropdownButton( value: selectedType, isExpanded: true, items: ApiKeyAppType.values .map((t) => DropdownMenuItem(value: t, child: Text(_appTypeName(t.value)))) .toList(), onChanged: (v) => setLocal(() => selectedType = v!), ), ], ), const SizedBox(height: 8), Row( children: [ Expanded( child: Text( selectedExpiration == null ? l.noExpiration : l.expiresOn('${selectedExpiration!.day.toString().padLeft(2, '0')}/${selectedExpiration!.month.toString().padLeft(2, '0')}/${selectedExpiration!.year}'), style: const TextStyle(fontSize: 14), ), ), TextButton( onPressed: () async { final picked = await showDatePicker( context: ctx2, initialDate: DateTime.now().add(const Duration(days: 365)), firstDate: DateTime.now().add(const Duration(days: 1)), lastDate: DateTime.now().add(const Duration(days: 365 * 5)), ); if (picked != null) setLocal(() => selectedExpiration = picked); }, child: Text(l.choose), ), if (selectedExpiration != null) IconButton( icon: const Icon(Icons.close, size: 16), onPressed: () => setLocal(() => selectedExpiration = null), tooltip: l.removeExpiration, ), ], ), ]), actions: [ TextButton(onPressed: () => Navigator.of(ctx2, rootNavigator: true).pop(), child: Text(l.cancel)), ElevatedButton( onPressed: () async { Navigator.of(ctx2, rootNavigator: true).pop(); final plainKey = await _createKey(ctx, nameCtrl.text, selectedType, selectedExpiration); if (plainKey != null && context.mounted) { _showPlainKeyDialog(context, plainKey); } }, child: Text(l.create), ), ], ); }), ); } void _showPlainKeyDialog(BuildContext context, String plainKey) { final l = AppLocalizations.of(context)!; showDialog( context: context, barrierDismissible: false, builder: (_) => AlertDialog( title: Text(l.apiKeyCreatedTitle), content: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l.copyKeyWarning, style: const TextStyle(color: Colors.orange, fontWeight: FontWeight.w600), ), const SizedBox(height: 12), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(4), border: Border.all(color: Colors.grey.shade300), ), child: Row(children: [ Expanded(child: SelectableText(plainKey, style: const TextStyle(fontFamily: 'monospace', fontSize: 13))), IconButton( icon: const Icon(Icons.copy, size: 18), tooltip: l.copy, onPressed: () => Clipboard.setData(ClipboardData(text: plainKey)), ), ]), ), ]), actions: [ ElevatedButton( onPressed: () => Navigator.of(context, rootNavigator: true).pop(), child: Text(l.copiedKey), ), ], ), ); } void _confirmRevoke(BuildContext context, ManagerAppContext ctx, Map key) { final l = AppLocalizations.of(context)!; showDialog( context: context, builder: (dialogContext) => AlertDialog( title: Text(l.revokeApiKeyTitle), content: Text(l.revokeApiKeyConfirm(key['name'] as String? ?? '')), actions: [ TextButton(onPressed: () => Navigator.pop(dialogContext), child: Text(l.cancel)), ElevatedButton( style: ElevatedButton.styleFrom(backgroundColor: Colors.red), onPressed: () async { Navigator.pop(dialogContext); await _revokeKey(ctx, key['id'] as String); }, child: Text(l.revoke, style: const TextStyle(color: Colors.white)), ), ], ), ); } @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { final ctx = Provider.of(context, listen: false).getContext() as ManagerAppContext; _loadKeys(ctx); }); } @override Widget build(BuildContext context) { final l = AppLocalizations.of(context)!; final appContext = Provider.of(context); final managerCtx = appContext.getContext() as ManagerAppContext; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(l.apiKeysTitle, style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600, color: kPrimaryColor)), ElevatedButton.icon( onPressed: () => _showCreateDialog(context, managerCtx), icon: const Icon(Icons.add), label: Text(l.createKeyBtn), ), ], ), const SizedBox(height: 16), if (_loading) const CommonLoader() else if (_keys.isEmpty) Center(child: Text(l.noApiKeys)) else Expanded( child: Card( elevation: 0, color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: Colors.grey.shade200), ), clipBehavior: Clip.antiAlias, child: SingleChildScrollView( child: SizedBox( width: double.infinity, child: DataTable( horizontalMargin: 16, columnSpacing: 24, headingRowColor: WidgetStateProperty.all(Colors.grey.shade50), dividerThickness: 1, columns: [ DataColumn(label: Text(l.name, style: const TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.type, style: const TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.createdOn, style: const TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.expiration, style: const TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.status, style: const TextStyle(fontWeight: FontWeight.w600))), DataColumn(label: Text(l.actions, style: const TextStyle(fontWeight: FontWeight.w600))), ], rows: _keys.map((key) { final isActive = key['isActive'] as bool? ?? false; final dateRaw = key['dateCreation'] as String?; final date = dateRaw != null ? DateTime.tryParse(dateRaw)?.toLocal() : null; final dateStr = date != null ? '${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}' : '—'; final expRaw = key['dateExpiration'] as String?; final exp = expRaw != null ? DateTime.tryParse(expRaw)?.toLocal() : null; final expStr = exp != null ? '${exp.day.toString().padLeft(2, '0')}/${exp.month.toString().padLeft(2, '0')}/${exp.year}' : '—'; return DataRow(cells: [ DataCell(Text(key['name'] as String? ?? '')), DataCell(Text(_appTypeName(key['appType']))), DataCell(Text(dateStr, style: const TextStyle(color: Colors.grey))), DataCell(Text(expStr, style: TextStyle(color: exp != null && exp.isBefore(DateTime.now()) ? Colors.red : Colors.grey))), DataCell(Chip( label: Text( isActive ? l.activeKey : l.revokedKey, style: TextStyle( color: isActive ? Colors.green.shade700 : Colors.red.shade700, fontSize: 12, ), ), backgroundColor: isActive ? Colors.green.shade50 : Colors.red.shade50, side: BorderSide(color: isActive ? Colors.green.shade200 : Colors.red.shade200), padding: const EdgeInsets.symmetric(horizontal: 4), visualDensity: VisualDensity.compact, )), DataCell(isActive ? IconButton( icon: const Icon(Icons.block, color: Colors.red, size: 20), tooltip: l.tooltipRevoke, onPressed: () => _confirmRevoke(context, managerCtx, key), ) : const SizedBox()), ]); }).toList(), ), ), ), ), ), ], ); } }