diff --git a/lib/src/ui/pages/details.dart b/lib/src/ui/pages/details.dart index a2e80f4..0057a42 100644 --- a/lib/src/ui/pages/details.dart +++ b/lib/src/ui/pages/details.dart @@ -5,40 +5,23 @@ import 'package:anitrack/src/ui/bloc/details_bloc.dart'; import 'package:anitrack/src/ui/constants.dart'; import 'package:anitrack/src/ui/widgets/dropdown.dart'; import 'package:anitrack/src/ui/widgets/image.dart'; +import 'package:anitrack/src/ui/widgets/integer_input.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; -class DetailsPage extends StatefulWidget { - DetailsPage({ +class DetailsPage extends StatelessWidget { + const DetailsPage({ super.key, }); static MaterialPageRoute get route => MaterialPageRoute( - builder: (_) => DetailsPage(), + builder: (_) => const DetailsPage(), settings: const RouteSettings( name: detailsRoute, ), ); - @override - DetailsPageState createState() => DetailsPageState(); -} - -class DetailsPageState extends State { - final TextEditingController _volumesOwnedController = TextEditingController(); - - @override - void initState() { - super.initState(); - - final state = GetIt.I.get().state; - - if (state.trackingType == TrackingMediumType.manga) { - _volumesOwnedController.text = '${(state.data as MangaTrackingData).volumesOwned}'; - } - } - @override Widget build(BuildContext context) { return Scaffold( @@ -137,78 +120,62 @@ class DetailsPageState extends State { ), ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + ), + child: IntegerInput( + labelText: state.trackingType == TrackingMediumType.anime ? + 'Episodes' : + 'Chapters', + onChanged: (value) { + switch (state.trackingType) { + case TrackingMediumType.anime: + final data = state.data as AnimeTrackingData; + context.read().add( + DetailsUpdatedEvent( + data.copyWith( + episodesWatched: value, + ), + ), + ); + break; + case TrackingMediumType.manga: + final data = state.data as MangaTrackingData; + context.read().add( + DetailsUpdatedEvent( + data.copyWith( + chaptersRead: value, + ), + ), + ); + break; + } + }, + initialValue: state.trackingType == TrackingMediumType.anime ? + (state.data as AnimeTrackingData).episodesWatched : + (state.data as MangaTrackingData).chaptersRead, + ), + ), + if (state.trackingType == TrackingMediumType.manga) Padding( padding: const EdgeInsets.symmetric( vertical: 8, ), - child: Row( - children: [ - ElevatedButton( - onPressed: () { - final data = (state.data as MangaTrackingData); - if (data.volumesOwned == 0) return; - - _volumesOwnedController.text = '${data.volumesOwned - 1}'; - context.read().add( - DetailsUpdatedEvent( - data.copyWith( - volumesOwned: data.volumesOwned - 1, - ), - ), - ); - }, - child: Icon(Icons.remove), - ), - - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - ), - child: TextField( - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Volumes owned', - ), - keyboardType: TextInputType.numberWithOptions( - signed: false, - decimal: false, - ), - textInputAction: TextInputAction.done, - controller: _volumesOwnedController, - onSubmitted: (value) { - final amount = int.parse(value); - if (amount < 0) return; - - context.read().add( - DetailsUpdatedEvent( - (state.data as MangaTrackingData).copyWith( - volumesOwned: amount, - ), - ), - ); - }, + child: IntegerInput( + labelText: 'Volumes owned', + onChanged: (value) { + final data = state.data as MangaTrackingData; + context.read().add( + DetailsUpdatedEvent( + data.copyWith( + volumesOwned: value, ), ), - ), - - ElevatedButton( - onPressed: () { - final data = (state.data as MangaTrackingData); - - _volumesOwnedController.text = '${data.volumesOwned + 1}'; - context.read().add( - DetailsUpdatedEvent( - data.copyWith( - volumesOwned: data.volumesOwned + 1, - ), - ), - ); - }, - child: Icon(Icons.add), - ), - ], + ); + }, + initialValue: (GetIt.I.get().state.data as MangaTrackingData).volumesOwned, ), ), ], diff --git a/lib/src/ui/widgets/integer_input.dart b/lib/src/ui/widgets/integer_input.dart new file mode 100644 index 0000000..2f7e638 --- /dev/null +++ b/lib/src/ui/widgets/integer_input.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; + +class IntegerInput extends StatefulWidget { + const IntegerInput({ + required this.onChanged, + required this.initialValue, + required this.labelText, + super.key, + }); + + /// Called when the integer value has been changed. + final void Function(int) onChanged; + + /// The label of the TextField. + final String labelText; + + /// The initial integer value. + final int initialValue; + + @override + IntegerInputState createState() => IntegerInputState(); +} + +class IntegerInputState extends State { + /// The integer value backing the widget + late int _value; + + /// The controller for the TextField. + final TextEditingController _controller = TextEditingController(); + + @override + void initState() { + super.initState(); + + _value = widget.initialValue; + _controller.text = _value.toString(); + } + + @override + Widget build(BuildContext context) { + return Row( + children: [ + ElevatedButton( + onPressed: () { + if (_value == 0) return; + + _value--; + _controller.text = '$_value'; + widget.onChanged(_value); + }, + child: const Icon(Icons.remove), + ), + + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: TextField( + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: widget.labelText, + ), + keyboardType: TextInputType.number, + textInputAction: TextInputAction.done, + controller: _controller, + onSubmitted: (text) { + final value = int.parse(text); + if (value < 0) return; + + _value = value; + _controller.text = '$_value'; + widget.onChanged(_value); + }, + ), + ), + ), + + ElevatedButton( + onPressed: () { + _value++; + _controller.text = '$_value'; + widget.onChanged(_value); + }, + child: const Icon(Icons.add), + ), + ], + ); + } +}