Untitled

mail@pastecode.io avatar
unknown
dart
a year ago
19 kB
6
Indexable
Never
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,
      );
    }
  }
}