Untitled
unknown
dart
2 years ago
19 kB
12
Indexable
import 'dart:convert'; import 'dart:io'; import 'dart:developer' as developer; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart' as http; import 'package:go_router/go_router.dart'; import 'package:jiffy/jiffy.dart'; import 'package:farma_mobile/models/session.dart'; import 'package:farma_mobile/components/navigation_drawer.dart' as navigation; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; class ScreenArguments { final int animalId; ScreenArguments(this.animalId); } class ProfileScreen extends StatefulWidget { const ProfileScreen({super.key, required this.animalId}); final String animalId; @override State<ProfileScreen> createState() => _ProfileScreenState(); } class _ProfileScreenState extends State<ProfileScreen> { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( backgroundColor: Theme.of(context).primaryColor, iconTheme: const IconThemeData(color: Colors.white), ), drawer: const navigation.NavigationDrawer(), body: Container( decoration: const BoxDecoration( color: Colors.white, ), child: Column( children: [ FutureBuilder( future: _fetchAnimal(), builder: (context, snapshot) { List<Widget> children = []; if (snapshot.hasData && snapshot.data!.statusCode == 200) { return _buildAnimalProfile( jsonDecode(snapshot.data!.body), ); } else if (snapshot.connectionState == ConnectionState.waiting) { children = <Widget>[ const SizedBox( width: 60, height: 60, child: CircularProgressIndicator(), ), Padding( padding: const EdgeInsets.only(top: 16), child: Image.asset( 'assets/images/loader.gif', width: 100.0, ), ) ]; } else { children = []; } return Column( children: children, ); }, ), ], ), ), ); } Widget _buildAnimalProfile(Map data) { final DateFormat formatter = DateFormat('dd.MM.yyyy'); // Age is shown in months DateTime birthdateRaw = DateTime.parse(data['birthdate']).toLocal(); Jiffy birthdate = Jiffy.parseFromDateTime(birthdateRaw); num oldMonths = Jiffy.now().diff(birthdate, unit: Unit.month); return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: [ Container( decoration: BoxDecoration( borderRadius: const BorderRadius.vertical( bottom: Radius.circular(25.0), ), color: Theme.of(context).primaryColor, ), child: Column( children: [ Padding( padding: const EdgeInsets.only( top: 25.0, left: 20.0, right: 20.0, bottom: 10.0, ), child: Row( children: [ Expanded( flex: 1, child: Consumer<SessionModel>( builder: (context, session, child) { if (session.user.isNotEmpty && session.user['is_admin']) { return CircleAvatar( radius: 25, backgroundColor: const Color.fromARGB(64, 255, 255, 255), child: IconButton( onPressed: () => context .push('/update/${data["animal_id"]}'), icon: const Icon( Icons.edit, color: Colors.white, ), ), ); } else { return Container(); } }, ), ), Expanded( flex: 2, child: Stack( alignment: Alignment.center, children: [ const CircleAvatar( radius: 60, backgroundColor: Colors.white, child: CircleAvatar( radius: 57, foregroundImage: AssetImage('assets/images/cow-example.png'), ), ), if (!data['is_expired']) Transform.translate( offset: const Offset(35, -45), child: Container( width: 15.0, height: 15.0, decoration: BoxDecoration( border: Border.all( width: 1.0, color: Colors.white, ), shape: BoxShape.circle, // if cow is inside red, if outside green color: data['most_recent_out'] != null && data['most_recent_out'] ['closed_at'] == null ? const Color(0xff1abc9c) : const Color(0xffe74c3c), ), ), ) ], ), ), Expanded( flex: 1, child: CircleAvatar( radius: 25, backgroundColor: const Color.fromARGB(64, 255, 255, 255), child: data['is_expired'] // Animal is sold or this deceased, nothing to do ? const Icon( Icons.not_interested, color: Colors.white, ) : _buildHandleOutButton( data['most_recent_out'], ), ), ), ], ), ), Text( _buildMostRecentOutLabel(data['most_recent_out']), style: const TextStyle( color: Color.fromARGB(170, 255, 255, 255), fontWeight: FontWeight.w300, fontSize: 12.0, ), ), Padding( padding: const EdgeInsets.only( top: 10.0, left: 20.0, right: 20.0, bottom: 25.0), child: SegmentedButton<int>( style: ButtonStyle( iconColor: const MaterialStatePropertyAll(Colors.white), backgroundColor: MaterialStateProperty.resolveWith<Color>( (Set<MaterialState> states) { if (states.contains(MaterialState.selected)) { return const Color(0xff1abc9c); } return const Color.fromARGB(28, 255, 255, 255); }, ), foregroundColor: const MaterialStatePropertyAll(Colors.white), textStyle: const MaterialStatePropertyAll( TextStyle(fontSize: 12), ), side: const MaterialStatePropertyAll( BorderSide(width: 0), ), shape: MaterialStatePropertyAll( RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.0), ), ), ), segments: const <ButtonSegment<int>>[ ButtonSegment<int>( value: 1, label: Text('Zdrava'), icon: Icon(Icons.health_and_safety)), ButtonSegment<int>( value: 2, label: Text('Bolesna'), icon: Icon(Icons.vaccines)), ButtonSegment<int>( value: 3, label: Text('Povrijeđena'), icon: Icon(Icons.personal_injury)), ], selected: {data['health']}, showSelectedIcon: false, ), ), ], ), ), SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.01, ), child: Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const SizedBox( height: 5.0, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _buildInfoPair( const IconData((0xe0c8), fontFamily: 'MaterialIcons'), 'Ime', data['name']), _buildInfoPair( const IconData(0xe3c5, fontFamily: 'MaterialIcons'), 'Pol', data['sex'] == 1 ? 'muški' : 'ženski'), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _buildInfoPair( const IconData(0xf06bb, fontFamily: 'MaterialIcons'), 'Starost', "$oldMonths mj."), if (data['passport'] != null) _buildInfoPair( const IconData(0xe1f2, fontFamily: 'MaterialIcons'), 'Pasoš', data['passport'], ), ], ), Row( children: [ _buildInfoPair( const IconData(0xe4f5, fontFamily: 'MaterialIcons'), 'Tag', data['tag']), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _buildInfoPair( const IconData(0xe038, fontFamily: 'MaterialIcons'), 'Dodato', formatter.format( DateTime.parse(data['created_at']).toLocal(), ), ), if (data['is_expired']) _buildInfoPair( const IconData(0xe570, fontFamily: 'MaterialIcons'), 'Prodato', formatter.format( DateTime.parse(data['expired_at']).toLocal(), ), ), ], ), const SizedBox( height: 20, ), ], ), ), ), ], ); } Widget _buildInfoPair(IconData icon, String key, String value) { return Expanded( flex: 1, child: Padding( padding: EdgeInsets.symmetric( vertical: 2.0, horizontal: MediaQuery.of(context).size.width * 0.02, ), child: Card( elevation: 2, color: Colors.white, surfaceTintColor: Colors.white, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 15.0), child: Row( children: [ Column( children: [ Padding( padding: const EdgeInsets.only( right: 15.0, ), child: Icon( icon, color: const Color(0xff1abc9c), size: 30.0, ), ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( key, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 19.0, ), ), const SizedBox( height: 2.0, ), Text( value, style: const TextStyle( color: Color.fromARGB(255, 90, 90, 90), fontSize: 12.0, ), ), ], ), ], ), ), ), ), ); } String _buildMostRecentOutLabel(Map? outData) { if (outData == null) { return 'Bez zabilježenih izlazaka'; } final DateFormat formatter = DateFormat('dd.MM.yyyy HH:mm'); if (outData['closed_at'] == null) { // Animal is outside return 'Izlazak: ${formatter.format(DateTime.parse(outData["created_at"]).toLocal())}'; } // Animal is inside return 'Zadnji izlazak: ${formatter.format(DateTime.parse(outData["created_at"]).toLocal())}'; } Widget _buildHandleOutButton(Map? outData) { if (outData != null && outData['closed_at'] == null) { return IconButton( onPressed: () { final snackBar = SnackBar( content: const Text( 'Da li stvarno želite da ručno zatvorite postojeći izlazak?', ), showCloseIcon: true, action: SnackBarAction( label: 'Da', onPressed: () => _closeOut( outData['out_id'], ), ), ); ScaffoldMessenger.of(context).showSnackBar(snackBar); }, icon: const Icon( Icons.close, color: Colors.white, ), ); } // Anima either had no previous outs or is inside return IconButton( onPressed: () { final snackBar = SnackBar( content: const Text( 'Da li stvarno želite da ručno uneste izlazak?', ), showCloseIcon: true, action: SnackBarAction( label: 'Da', onPressed: () => _createOut( int.parse( widget.animalId, ), ), ), ); ScaffoldMessenger.of(context).showSnackBar(snackBar); }, icon: const Icon( Icons.rocket_launch, color: Colors.white, ), ); } Future<http.Response> _fetchAnimal() async { const storage = FlutterSecureStorage(); final String? apiKey = await storage.read(key: dotenv.get('BACKEND_TOKEN_KEY')); return http.get( Uri.http( dotenv.get('API_URL'), '/animals/${widget.animalId}/', ), headers: { HttpHeaders.authorizationHeader: 'bearer $apiKey', }, ); } void _createOut(int animalId) async { try { const storage = FlutterSecureStorage(); final String? apiKey = await storage.read( key: dotenv.get('BACKEND_TOKEN_KEY'), ); final http.Response response = await http.post( Uri.http(dotenv.get('API_URL'), '/outs/manually/'), headers: { HttpHeaders.authorizationHeader: 'bearer $apiKey', HttpHeaders.contentTypeHeader: 'application/json; charset=UTF-8', }, body: jsonEncode({ 'animal_id': animalId, }), ); if (response.statusCode == 200) { if (!mounted) return; setState(() {}); } } catch (err) { // TODO: Handle error - show error dialog developer.log( 'Error creating the out', name: 'profile', error: err, ); } } void _closeOut(int outId) async { try { const storage = FlutterSecureStorage(); final String? apiKey = await storage.read( key: dotenv.get('BACKEND_TOKEN_KEY'), ); final http.Response response = await http.put( Uri.http(dotenv.get('API_URL'), '/outs/'), headers: { HttpHeaders.authorizationHeader: 'bearer $apiKey', HttpHeaders.contentTypeHeader: 'application/json; charset=UTF-8', }, body: jsonEncode({ 'out_id': outId, }), ); if (response.statusCode == 200) { if (!mounted) return; setState(() {}); } } catch (err) { // TODO: Handle error - show error dialog developer.log( 'Error closing the out', name: 'profile', error: err, ); } } }
Editor is loading...