Created ProgressViewWidget and rewritten ProgressDetailScreen with it

Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
This commit is contained in:
Andreas Fahrecker 2021-02-11 20:25:24 +01:00
parent 1abfd0c1f5
commit 1826bbe421
6 changed files with 166 additions and 189 deletions

View File

@ -98,7 +98,7 @@ class TimeProgress {
} }
static bool isNameValid(String name) { static bool isNameValid(String name) {
return name != null && name != "" && name.length > 3 && name.length < 20; return name != null && name != "" && name.length > 2 && name.length < 21;
} }
static bool areTimesValid(DateTime startTime, DateTime endTime) { static bool areTimesValid(DateTime startTime, DateTime endTime) {

View File

@ -2,10 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:time_progress_tracker/actions/actions.dart'; import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/app_exceptions.dart';
import 'package:time_progress_tracker/models/app_state.dart'; import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_tracker/models/time_progress.dart'; import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/widgets/date_picker_btn.dart';
import 'package:time_progress_tracker/widgets/progress_editor_widget.dart'; import 'package:time_progress_tracker/widgets/progress_editor_widget.dart';
class ProgressCreationScreen extends StatefulWidget { class ProgressCreationScreen extends StatefulWidget {

View File

@ -1,20 +1,16 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:time_progress_tracker/actions/actions.dart'; import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/app_exceptions.dart';
import 'package:time_progress_tracker/models/app_state.dart'; import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_tracker/models/time_progress.dart'; import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/screens/home_screen.dart'; import 'package:time_progress_tracker/screens/home_screen.dart';
import 'package:time_progress_tracker/selectors/time_progress_selectors.dart'; import 'package:time_progress_tracker/selectors/time_progress_selectors.dart';
import 'package:time_progress_tracker/widgets/app_yes_no_dialog_widget.dart'; import 'package:time_progress_tracker/widgets/app_yes_no_dialog_widget.dart';
import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_circular_percent_widget.dart';
import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_edit_dates_row_widget.dart';
import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_fab_editing_row_widget.dart'; import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_fab_editing_row_widget.dart';
import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_fab_row_widget.dart'; import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_fab_row_widget.dart';
import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_linear_percent_widget.dart'; import 'package:time_progress_tracker/widgets/progress_editor_widget.dart';
import 'package:time_progress_tracker/widgets/progress_view_widget.dart';
class ProgressDetailScreenArguments { class ProgressDetailScreenArguments {
final String id; final String id;
@ -32,69 +28,55 @@ class ProgressDetailScreen extends StatefulWidget {
} }
class _ProgressDetailScreenState extends State<ProgressDetailScreen> { class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
bool _isBeingEdited = false; bool _editMode = false;
final TextEditingController _nameController = TextEditingController();
TimeProgress _editedProgress = TimeProgress.initialDefault(); TimeProgress _editedProgress = TimeProgress.initialDefault();
bool _validName = true; void _onEditedProgressChanged(TimeProgress newProgress) {
setState(() {
void _onStartDateChanged(DateTime picked) { _editedProgress = newProgress;
if (picked != null) { });
setState(() {
_editedProgress = _editedProgress.copyWith(startTime: picked);
});
}
}
void _onEndDateChanged(DateTime picked) {
if (picked != null) {
setState(() {
_editedProgress = _editedProgress.copyWith(endTime: picked);
});
}
} }
void _onSaveTimeProgress(Store<AppState> store, id) { void _onSaveTimeProgress(Store<AppState> store, id) {
if (!TimeProgress.isValid(_editedProgress)) return;
store.dispatch(UpdateTimeProgressAction(id, _editedProgress)); store.dispatch(UpdateTimeProgressAction(id, _editedProgress));
setState(() { setState(() {
_isBeingEdited = false; _editMode = false;
}); });
} }
void _showCancelEditTimeProgressDialog(AppState state, id) { void _showCancelEditTimeProgressDialog(AppState state, id) {
TimeProgress originalTp = timeProgressByIdSelector(state, id); TimeProgress originalTp = timeProgressByIdSelector(state, id);
if (originalTp != _editedProgress) { if (originalTp != _editedProgress) {
String originalName = timeProgressByIdSelector(state, id).name; String originalName = originalTp.name;
showDialog( showDialog(
context: context, context: context,
builder: (_) => AppYesNoDialog( builder: (_) => AppYesNoDialog(
titleText: "Cancel Editing of $originalName", titleText: "Cancel Editing of $originalName",
contentText: contentText:
"Are you sure that you want to discard the changes done to $originalName", "Are you sure that you want to discard the changes done to $originalName",
onYesPressed: _onCancelEditTimeProgress, onYesPressed: () {
_cancelEditMode();
Navigator.pop(context);
},
onNoPressed: _onCloseDialog, onNoPressed: _onCloseDialog,
), ),
); );
} else { } else {
setState(() { _cancelEditMode();
_isBeingEdited = false;
});
} }
} }
void _onCancelEditTimeProgress() { void _cancelEditMode() {
setState(() { setState(() {
_isBeingEdited = false; _editMode = false;
}); });
Navigator.pop(context);
} }
void _onEditTimeProgress(Store<AppState> store, id) { void _onEditTimeProgress(AppState state, id) {
setState(() { setState(() {
_isBeingEdited = true; _editMode = true;
_editedProgress = timeProgressByIdSelector(store.state, id); _editedProgress = timeProgressByIdSelector(state, id);
_nameController.text = _editedProgress.name;
}); });
} }
@ -119,38 +101,17 @@ class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
Navigator.pop(context); Navigator.pop(context);
} }
@override
void initState() {
super.initState();
_nameController.addListener(() {
try {
TimeProgress editedProgress =
_editedProgress.copyWith(name: _nameController.text);
setState(() {
_editedProgress = editedProgress;
_validName = true;
});
} on TimeProgressInvalidNameException catch (e) {
setState(() {
_validName = false;
});
}
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ProgressDetailScreenArguments args = final ProgressDetailScreenArguments args =
ModalRoute.of(context).settings.arguments; ModalRoute.of(context).settings.arguments;
final Store<AppState> store = StoreProvider.of<AppState>(context); final Store<AppState> store = StoreProvider.of<AppState>(context);
final ThemeData appTheme = Theme.of(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("Progress"), title: Text("Progress"),
), ),
/*drawer: AppDrawer(
appVersion: widget.appVersion,
),*/
body: Container( body: Container(
margin: EdgeInsets.all(8), margin: EdgeInsets.all(8),
child: StoreConnector( child: StoreConnector(
@ -162,119 +123,65 @@ class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
return Center( return Center(
child: Text("Error Invalid Time Progress"), child: Text("Error Invalid Time Progress"),
); );
List<Widget> columnChildren = [
Expanded(
child: ProgressViewWidget(
timeProgress:
_editMode ? _editedProgress : vm.timeProgress),
)
];
if (_editMode)
columnChildren.add(Expanded(
child: ProgressEditorWidget(
timeProgress: _editedProgress,
onTimeProgressChanged: _onEditedProgressChanged,
),
));
return Column( return Column(
children: <Widget>[ children: columnChildren,
Expanded(
flex: 1,
child: _isBeingEdited
? TextField(
controller: _nameController,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Progress Name",
errorText: _validName
? null
: "The Name of the Time Progress has to be set.",
),
)
: (vm.timeProgress.hasStarted() &&
!vm.timeProgress.hasEnded())
? FittedBox(
fit: BoxFit.fitWidth,
child: Text(
vm.timeProgress.name,
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
)
: Center(
child: FittedBox(
fit: BoxFit.fitWidth,
child: Text(
vm.timeProgress.name,
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
),
),
),
(vm.timeProgress.hasStarted() && !vm.timeProgress.hasEnded())
? Expanded(
flex: 2,
child: ProgressDetailCircularPercent(
percentDone: _isBeingEdited
? _editedProgress.percentDone()
: vm.timeProgress.percentDone(),
),
)
: Expanded(
flex: 2,
child: !vm.timeProgress.hasEnded()
? Text(
"Starts in ${vm.timeProgress.startTime.difference(DateTime.now()).inDays} Days.")
: Text(
"Ended ${DateTime.now().difference(vm.timeProgress.endTime).inDays} Days ago."),
),
(vm.timeProgress.hasStarted() && !vm.timeProgress.hasEnded())
? Expanded(
flex: 1,
child: ProgressDetailLinearPercent(
timeProgress: _isBeingEdited
? _editedProgress
: vm.timeProgress,
),
)
: Spacer(
flex: 1,
),
Expanded(
flex: 1,
child: Text(
"${_isBeingEdited ? _editedProgress.allDays() : vm.timeProgress.allDays()} Days"),
),
this._isBeingEdited
? Expanded(
flex: 1,
child: ProgressDetailEditDatesRow(
startTime: _editedProgress.startTime,
endTime: _editedProgress.endTime,
onStartTimeChanged: _onStartDateChanged,
onEndTimeChanged: _onEndDateChanged,
),
)
: Spacer(flex: 1),
Spacer(flex: 1)
],
); );
}, },
), ),
), ),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: _isBeingEdited floatingActionButton: Row(
? ProgressDetailFabEditingRow( children: [
onSave: () => Expanded(
_validName ? _onSaveTimeProgress(store, args.id) : null, child: FloatingActionButton(
onCancelEdit: () => heroTag: _editMode
_showCancelEditTimeProgressDialog(store.state, args.id), ? "saveEditedTimeProgressBTN"
) : "editTimeProgressBTN",
: ProgressDetailFabRow( child: _editMode ? Icon(Icons.save) : Icon(Icons.edit),
onEdit: () => _onEditTimeProgress(store, args.id), backgroundColor: _editMode ? Colors.green : appTheme.accentColor,
onDelete: () => _showDeleteTimeProgressDialog(store, args.id), onPressed: _editMode
? () {
_onSaveTimeProgress(store, args.id);
}
: () {
_onEditTimeProgress(store.state, args.id);
},
), ),
),
Expanded(
child: FloatingActionButton(
heroTag: _editMode
? "cancelEditTimeProgressBTN"
: "deleteTimeProgressBTN",
child: _editMode ? Icon(Icons.cancel) : Icon(Icons.delete),
backgroundColor: Colors.red,
onPressed: _editMode
? () {
_showCancelEditTimeProgressDialog(store.state, args.id);
}
: () {
_showDeleteTimeProgressDialog(store, args.id);
},
),
),
],
),
); );
} }
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
} }
class _ViewModel { class _ViewModel {

View File

@ -18,41 +18,53 @@ class ProgressEditorWidget extends StatefulWidget {
} }
class _ProgressEditorWidgetState extends State<ProgressEditorWidget> { class _ProgressEditorWidgetState extends State<ProgressEditorWidget> {
final _nameTextController = TextEditingController();
bool _validName = true, _validDate = true; bool _validName = true, _validDate = true;
void _onNameChanged(String newName) { void _onNameChanged() {
if (!TimeProgress.isNameValid(newName)) if (TimeProgress.isNameValid(_nameTextController.text)) {
widget.onTimeProgressChanged(
widget.timeProgress.copyWith(name: _nameTextController.text));
setState(() {
_validName = true;
});
} else
setState(() { setState(() {
_validName = false; _validName = false;
}); });
widget.onTimeProgressChanged(widget.timeProgress.copyWith(name: newName));
setState(() {
_validName = true;
});
} }
void _onStartDateChanged(DateTime newStartDate) { void _onStartDateChanged(DateTime newStartDate) {
if (!TimeProgress.areTimesValid(newStartDate, widget.timeProgress.endTime)) if (TimeProgress.areTimesValid(newStartDate, widget.timeProgress.endTime)) {
widget.onTimeProgressChanged(
widget.timeProgress.copyWith(startTime: newStartDate));
setState(() {
_validDate = true;
});
} else
setState(() { setState(() {
_validDate = false; _validDate = false;
}); });
widget.onTimeProgressChanged(
widget.timeProgress.copyWith(startTime: newStartDate));
setState(() {
_validDate = true;
});
} }
void _onEndDateChanged(DateTime newEndDate) { void _onEndDateChanged(DateTime newEndDate) {
if (!TimeProgress.areTimesValid(widget.timeProgress.startTime, newEndDate)) if (TimeProgress.areTimesValid(widget.timeProgress.startTime, newEndDate)) {
widget.onTimeProgressChanged(
widget.timeProgress.copyWith(endTime: newEndDate));
setState(() {
_validDate = true;
});
} else
setState(() { setState(() {
_validDate = false; _validDate = false;
}); });
}
widget.onTimeProgressChanged( @override
widget.timeProgress.copyWith(endTime: newEndDate)); void initState() {
_nameTextController.text = widget.timeProgress.name;
_nameTextController.addListener(_onNameChanged);
super.initState();
} }
@override @override
@ -60,12 +72,13 @@ class _ProgressEditorWidgetState extends State<ProgressEditorWidget> {
List<Widget> columnChildren = [ List<Widget> columnChildren = [
Expanded( Expanded(
child: TextField( child: TextField(
onChanged: _onNameChanged, controller: _nameTextController,
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: OutlineInputBorder(),
labelText: "Progress Name", labelText: "Progress Name",
errorText: errorText: _validName
_validName ? null : "The Name of the Progress can't be empty.", ? null
: "The Name need to have at least 3 and at max 20 symbols.",
), ),
), ),
), ),
@ -102,7 +115,9 @@ class _ProgressEditorWidgetState extends State<ProgressEditorWidget> {
Expanded( Expanded(
child: Center( child: Center(
child: Text( child: Text(
"Invalid Dates. The Start Date has to be before the End Date"), "Invalid Dates. The Start Date has to be before the End Date",
style: TextStyle(color: Colors.red),
),
), ),
), ),
); );

View File

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
class ProgressViewWidget extends StatelessWidget {
final TimeProgress timeProgress;
ProgressViewWidget({
@required this.timeProgress,
});
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Expanded(
child: FittedBox(
fit: BoxFit.fitWidth,
child: Text(
timeProgress.name,
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
),
),
Expanded(
child: CircularPercentIndicator(
radius: 100,
lineWidth: 10,
percent: timeProgress.percentDone(),
progressColor: Colors.green,
backgroundColor: Colors.red,
center: Text("${(timeProgress.percentDone() * 100).floor()} %"),
),
),
Expanded(
child: LinearPercentIndicator(
padding: EdgeInsets.symmetric(horizontal: 15),
percent: timeProgress.percentDone(),
leading: Text("${timeProgress.daysBehind()} Days"),
center: Text("${(timeProgress.percentDone() * 100).floor()} %"),
trailing: Text("${timeProgress.daysLeft()} Days"),
progressColor: Colors.green,
backgroundColor: Colors.red,
lineHeight: 25,
),
),
],
),
);
}
}

View File

@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.0.10+10 version: 0.0.11+11
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: ">=2.7.0 <3.0.0"