gestao_ponto_package 2.2.4
gestao_ponto_package: ^2.2.4 copied to clipboard
Flutter package with time tracking and attendance management features for Senior mobile apps.
example/lib/main.dart
import 'dart:convert';
import 'dart:developer';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:gestao_ponto_package/gestao_ponto_package.dart' as gestao;
import 'package:http/http.dart' as http;
import 'package:senior_design_system/senior_design_system.dart';
import 'package:senior_design_tokens/senior_design_tokens.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'features/app/module/app_module.dart';
import 'features/app/presentation/app_widget.dart';
import 'firebase_options.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(const LoginApp());
}
class LoginApp extends StatelessWidget {
const LoginApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(debugShowCheckedModeBanner: false, home: const LoginScreen());
}
}
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
bool _isLoading = false;
String? _errorMessage;
// URL options
final List<Map<String, String>> _urlOptions = [
{'name': 'Leaf (Cloud Leaf)', 'value': 'https://cloud-leaf.senior.com.br/t/senior.com.br/bridge/1.0/rest/'},
{'name': 'Homologação (Platform)', 'value': 'https://platform-homologx.senior.com.br/t/senior.com.br/bridge/1.0/rest/'},
];
String _selectedUrl = 'https://platform-homologx.senior.com.br/t/senior.com.br/bridge/1.0/rest/';
// Credenciais salvas
List<Map<String, String>> _savedCredentials = [];
String? _selectedCredential;
@override
void initState() {
super.initState();
_loadSavedCredentials();
}
Future<void> _authenticate() async {
setState(() {
_isLoading = true;
_errorMessage = null;
});
var username = _usernameController.text.trim();
var password = _passwordController.text.trim();
if (username.isEmpty || password.isEmpty) {
setState(() {
_isLoading = false;
_errorMessage = 'Por favor, preencha todos os campos.';
});
return;
}
try {
final url = '${_selectedUrl}platform/authentication/actions/login';
final requestBody = {'username': username, 'password': password};
// Log da request
log('🔍 === REQUEST LOG ===');
log('URL: $url');
log('Headers: {Content-Type: application/json}');
log('Body: ${jsonEncode(requestBody)}');
log('Selected URL for Config: $_selectedUrl');
log('========================');
final response = await http.post(Uri.parse(url), headers: {'Content-Type': 'application/json'}, body: jsonEncode(requestBody));
// Log da response
log('📥 === RESPONSE LOG ===');
log('Status Code: ${response.statusCode}');
log('Headers: ${response.headers}');
log('Body: ${response.body}');
log('======================');
if (response.statusCode != 200) {
// Limita o tamanho da mensagem de erro para evitar overflow
String errorBody = response.body;
if (errorBody.length > 200) {
errorBody = '${errorBody.substring(0, 200)}...';
}
throw Exception('Falha ao autenticar (${response.statusCode}): $errorBody');
}
final body = jsonDecode(response.body);
final jsonToken = jsonDecode(body['jsonToken'] as String);
final token = getTokenFromJson(jsonToken);
// Salva as credenciais após sucesso no login
await _saveCredentials(username, password);
if (!gestao.Config.isInitialized()) {
gestao.Config.init(_selectedUrl, token: token);
}
Modular.setInitialRoute('/');
final brightness = WidgetsBinding.instance.platformDispatcher.platformBrightness;
var theme = brightness == Brightness.dark ? SENIOR_DARK_THEME : SENIOR_LIGHT_THEME;
runApp(
ModularApp(
module: AppModule(),
child: SeniorDesignSystem(theme: theme, child: const AppWidget()),
),
);
} catch (e) {
setState(() {
_isLoading = false;
_errorMessage = 'Erro ao autenticar: $e';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: SeniorColors.primaryColor500,
body: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: MediaQuery.of(context).size.height),
child: IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Card(
color: SeniorColors.pureBlack,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Exemplo Biblioteca Gestão Ponto Package',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white),
),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
value: _selectedUrl,
decoration: const InputDecoration(
labelText: 'Ambiente',
labelStyle: TextStyle(color: Colors.white),
filled: true,
fillColor: Colors.white10,
border: OutlineInputBorder(),
),
dropdownColor: Colors.grey[800],
style: const TextStyle(color: Colors.white),
items: _urlOptions.map((option) {
return DropdownMenuItem<String>(
value: option['value'],
child: Text(option['name']!, style: const TextStyle(color: Colors.white)),
);
}).toList(),
onChanged: (String? newValue) {
if (newValue != null) {
setState(() {
_selectedUrl = newValue;
});
}
},
),
const SizedBox(height: 16),
if (_savedCredentials.isNotEmpty)
DropdownButtonFormField<String>(
value: _selectedCredential,
decoration: const InputDecoration(
labelText: 'Credenciais Salvas',
labelStyle: TextStyle(color: Colors.white),
filled: true,
fillColor: Colors.white10,
border: OutlineInputBorder(),
),
dropdownColor: Colors.grey[800],
style: const TextStyle(color: Colors.white),
items: [
const DropdownMenuItem<String>(
value: null,
child: Text('Selecione uma credencial...', style: TextStyle(color: Colors.white70)),
),
..._savedCredentials.map((credential) {
return DropdownMenuItem<String>(
value: credential['username'],
child: Text(credential['displayName'] ?? credential['username'] ?? '', style: const TextStyle(color: Colors.white)),
);
}),
],
onChanged: _selectCredential,
),
if (_savedCredentials.isNotEmpty) const SizedBox(height: 16),
TextField(
controller: _usernameController,
decoration: const InputDecoration(
labelText: 'Usuário',
labelStyle: TextStyle(color: Colors.white),
filled: true,
fillColor: Colors.white10,
border: OutlineInputBorder(),
),
style: const TextStyle(color: Colors.white),
),
const SizedBox(height: 16),
TextField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: 'Senha',
labelStyle: TextStyle(color: Colors.white),
filled: true,
fillColor: Colors.white10,
border: OutlineInputBorder(),
),
obscureText: true,
style: const TextStyle(color: Colors.white),
),
const SizedBox(height: 16),
if (_errorMessage != null)
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
border: Border.all(color: Colors.red),
borderRadius: BorderRadius.circular(8),
),
child: Text(
_errorMessage!,
style: const TextStyle(color: Colors.red, fontSize: 12),
textAlign: TextAlign.center,
softWrap: true,
overflow: TextOverflow.visible,
),
),
const SizedBox(height: 16),
_isLoading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: _authenticate,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: const Text('Entrar', style: TextStyle(color: Colors.white)),
),
],
),
),
),
),
),
),
),
),
);
}
Future<void> _loadSavedCredentials() async {
final prefs = await SharedPreferences.getInstance();
final credentialsJson = prefs.getStringList('saved_credentials') ?? [];
setState(() {
_savedCredentials = credentialsJson.map((cred) {
final decoded = jsonDecode(cred);
return Map<String, String>.from(decoded);
}).toList();
});
}
Future<void> _saveCredentials(String username, String password) async {
final prefs = await SharedPreferences.getInstance();
// Verifica se as credenciais já existem
final existingIndex = _savedCredentials.indexWhere((cred) => cred['username'] == username);
final newCredential = {
'username': username,
'password': password,
'displayName': username, // Pode ser customizado futuramente
};
if (existingIndex >= 0) {
// Atualiza credenciais existentes
_savedCredentials[existingIndex] = newCredential;
} else {
// Adiciona novas credenciais
_savedCredentials.add(newCredential);
}
// Limita a 5 credenciais salvas
if (_savedCredentials.length > 5) {
_savedCredentials = _savedCredentials.sublist(_savedCredentials.length - 5);
}
final credentialsJson = _savedCredentials.map((cred) => jsonEncode(cred)).toList();
await prefs.setStringList('saved_credentials', credentialsJson);
setState(() {});
}
void _selectCredential(String? credentialJson) {
if (credentialJson != null && credentialJson.isNotEmpty) {
final credential = _savedCredentials.firstWhere((cred) => cred['username'] == credentialJson, orElse: () => {});
if (credential.isNotEmpty) {
setState(() {
_selectedCredential = credentialJson;
_usernameController.text = credential['username'] ?? '';
_passwordController.text = credential['password'] ?? '';
});
}
} else {
setState(() {
_selectedCredential = null;
_usernameController.clear();
_passwordController.clear();
});
}
}
gestao.Token? getTokenFromJson(Map<String, dynamic>? body) {
if (body == null) {
return null;
}
return gestao.Token(
accessToken: gestao.tryCast<String>(body['access_token'], ''),
expiresIn: gestao.tryCast<int>(body['expires_in'], 0),
tokenType: gestao.tryCast<String>(body['token_type'], ''),
refreshToken: gestao.tryCast<String>(body['refresh_token'], ''),
username: gestao.tryCast<String>(body['username'], ''),
);
}
}