Untitled
unknown
dart
2 years ago
19 kB
26
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...