Cleanup of Progress Detail Screen

Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
This commit is contained in:
Andreas Fahrecker 2021-02-11 23:38:02 +01:00
parent c09b164a61
commit 5c2592f601
10 changed files with 139 additions and 465 deletions

View File

@ -6,9 +6,7 @@ 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/detail_screen_floating_action_buttons.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_editor_widget.dart'; import 'package:time_progress_tracker/widgets/progress_editor_widget.dart';
import 'package:time_progress_tracker/widgets/progress_view_widget.dart'; import 'package:time_progress_tracker/widgets/progress_view_widget.dart';
@ -20,6 +18,7 @@ class ProgressDetailScreenArguments {
class ProgressDetailScreen extends StatefulWidget { class ProgressDetailScreen extends StatefulWidget {
static const routeName = "/progress-detail"; static const routeName = "/progress-detail";
static const title = "Progress View";
@override @override
State<StatefulWidget> createState() { State<StatefulWidget> createState() {
@ -29,7 +28,7 @@ class ProgressDetailScreen extends StatefulWidget {
class _ProgressDetailScreenState extends State<ProgressDetailScreen> { class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
bool _editMode = false; bool _editMode = false;
TimeProgress _editedProgress = TimeProgress.initialDefault(); TimeProgress _editedProgress;
void _onEditedProgressChanged(TimeProgress newProgress) { void _onEditedProgressChanged(TimeProgress newProgress) {
setState(() { setState(() {
@ -37,162 +36,70 @@ class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
}); });
} }
void _onSaveTimeProgress(Store<AppState> store, id) { void _switchEditMode(bool newMode) {
if (!TimeProgress.isValid(_editedProgress)) return;
store.dispatch(UpdateTimeProgressAction(id, _editedProgress));
setState(() { setState(() {
_editMode = false; _editMode = newMode;
}); });
} }
void _showCancelEditTimeProgressDialog(AppState state, id) {
TimeProgress originalTp = timeProgressByIdSelector(state, id);
if (originalTp != _editedProgress) {
String originalName = originalTp.name;
showDialog(
context: context,
builder: (_) => AppYesNoDialog(
titleText: "Cancel Editing of $originalName",
contentText:
"Are you sure that you want to discard the changes done to $originalName",
onYesPressed: () {
_cancelEditMode();
Navigator.pop(context);
},
onNoPressed: _onCloseDialog,
),
);
} else {
_cancelEditMode();
}
}
void _cancelEditMode() {
setState(() {
_editMode = false;
});
}
void _onEditTimeProgress(AppState state, id) {
setState(() {
_editMode = true;
_editedProgress = timeProgressByIdSelector(state, id);
});
}
void _showDeleteTimeProgressDialog(Store<AppState> store, id) {
showDialog(
context: context,
builder: (_) => AppYesNoDialog(
titleText: "Delete ${timeProgressByIdSelector(store.state, id).name}",
contentText: "Are you sure you want to delete this time progress?",
onYesPressed: () => _onDeleteTimeProgress(store, id),
onNoPressed: _onCloseDialog,
),
);
}
void _onDeleteTimeProgress(Store<AppState> store, String id) {
store.dispatch(DeleteTimeProgressAction(id));
Navigator.popUntil(context, ModalRoute.withName(HomeScreen.routeName));
}
void _onCloseDialog() {
Navigator.pop(context);
}
@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); final TimeProgress _timeProgress =
timeProgressByIdSelector(store.state, args.id);
if (_timeProgress == null) //+++++Time Progress Not Found Error+++++
return Center(
child: Text("Error Invalid Time Progress"),
);
if (_editedProgress == null)
_editedProgress = _timeProgress; // initialize _editedProgress
void _saveEditedProgress() {
store.dispatch(UpdateTimeProgressAction(args.id, _editedProgress));
_switchEditMode(false);
}
void _deleteTimeProgress() {
store.dispatch(DeleteTimeProgressAction(args.id));
Navigator.popUntil(context, ModalRoute.withName(HomeScreen.routeName));
}
List<Widget> columnChildren = [
Expanded(
child: ProgressViewWidget(
timeProgress: _editMode ? _editedProgress : _timeProgress),
)
];
if (_editMode)
columnChildren.add(Expanded(
child: ProgressEditorWidget(
timeProgress: _editedProgress,
onTimeProgressChanged: _onEditedProgressChanged,
),
));
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("Progress"), title: Text(ProgressDetailScreen.title),
), ),
body: Container( body: Container(
margin: EdgeInsets.all(8), margin: EdgeInsets.all(8),
child: StoreConnector( child: Column(
converter: (Store<AppState> store) => children: columnChildren,
_ViewModel.fromStoreAndArg(store, args),
onInit: loadTimeProgressListIfUnloaded,
builder: (BuildContext context, _ViewModel vm) {
if (vm.timeProgress == null)
return Center(
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(
children: columnChildren,
);
},
), ),
), ),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: Row( floatingActionButton: DetailScreenFloatingActionButtons(
children: [ editMode: _editMode,
Expanded( originalProgress: timeProgressByIdSelector(store.state, args.id),
child: FloatingActionButton( editedProgress: _editedProgress,
heroTag: _editMode onEditProgress: () => _switchEditMode(true),
? "saveEditedTimeProgressBTN" onSaveEditedProgress: _saveEditedProgress,
: "editTimeProgressBTN", onCancelEditProgress: () => _switchEditMode(false),
child: _editMode ? Icon(Icons.save) : Icon(Icons.edit), onDeleteProgress: _deleteTimeProgress),
backgroundColor: _editMode ? Colors.green : appTheme.accentColor,
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);
},
),
),
],
),
);
}
}
class _ViewModel {
final TimeProgress timeProgress;
_ViewModel({@required this.timeProgress});
static _ViewModel fromStoreAndArg(
Store<AppState> store, ProgressDetailScreenArguments args) {
return _ViewModel(
timeProgress: timeProgressByIdSelector(store.state, args.id),
); );
} }
} }

View File

@ -1,129 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/app.dart';
import 'package:time_progress_tracker/models/app_state.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/progress_detail_screen.dart';
import 'package:time_progress_tracker/selectors/time_progress_selectors.dart';
class AppDrawer extends StatelessWidget {
final String appVersion;
AppDrawer({
Key key,
@required this.appVersion,
}) : super(key: key);
@override
Widget build(BuildContext context) {
ThemeData appTheme = Theme.of(context);
return Drawer(
child: StoreConnector(
converter: _ViewModel.fromStore,
onInit: loadTimeProgressListIfUnloaded,
builder: (context, _ViewModel vm) {
if (!vm.hasLoaded) {
return Center(
child: CircularProgressIndicator(),
);
}
List<Widget> drawerTileList = List<Widget>();
drawerTileList.add(DrawerHeader(
child: Text(TimeProgressTrackerApp.name),
decoration: BoxDecoration(color: appTheme.primaryColor),
margin: EdgeInsets.zero,
));
drawerTileList.add(Container(
color: appTheme.accentColor,
margin: EdgeInsets.only(bottom: 8),
child: ListTile(
title: Text(HomeScreen.title),
trailing: Icon(Icons.dashboard),
onTap: () {
Navigator.pop(context);
Navigator.pushNamed(context, HomeScreen.routeName);
},
),
));
if (vm.currentTimeProgresses.length > 0) {
for (TimeProgress tp in vm.currentTimeProgresses) {
drawerTileList.add(ListTile(
title: Text(tp.name),
trailing: CircularPercentIndicator(
percent: tp.percentDone(),
radius: 40,
progressColor: Colors.green,
backgroundColor: Colors.red,
center: FittedBox(
fit: BoxFit.scaleDown,
child:
Text((tp.percentDone() * 100).floor().toString() + "%"),
),
),
onTap: () {
Navigator.pop(context);
Navigator.pushNamed(
context,
ProgressDetailScreen.routeName,
arguments: ProgressDetailScreenArguments(tp.id),
);
},
));
if (vm.currentTimeProgresses.last != tp) {
drawerTileList.add(Divider(
color: Colors.black12,
));
}
}
} else {
drawerTileList.add(ListTile(
title: Text("You don't have any tracked time progress."),
));
}
drawerTileList.add(Divider(
color: Colors.black38,
));
drawerTileList.add(Container(
margin: EdgeInsets.only(bottom: 8),
child: ListTile(
title: Text("About"),
onTap: () {
showAboutDialog(
context: context,
applicationName: TimeProgressTrackerApp.name,
applicationVersion: " Version $appVersion",
applicationLegalese: '\u00a9Andreas Fahrecker 2020-2021');
},
),
));
return ListView(
children: drawerTileList,
);
},
),
);
}
}
class _ViewModel {
final List<TimeProgress> currentTimeProgresses;
final bool hasLoaded;
_ViewModel({
@required this.currentTimeProgresses,
@required this.hasLoaded,
});
static _ViewModel fromStore(Store<AppState> store) {
return _ViewModel(
currentTimeProgresses: currentTimeProgressSelector(store.state),
hasLoaded: store.state.hasLoaded,
);
}
}

View File

@ -4,14 +4,12 @@ class AppYesNoDialog extends StatelessWidget {
final String titleText; final String titleText;
final String contentText; final String contentText;
final void Function() onYesPressed; final void Function() onYesPressed;
final void Function() onNoPressed;
AppYesNoDialog({ AppYesNoDialog({
Key key, Key key,
@required this.titleText, @required this.titleText,
@required this.contentText, @required this.contentText,
@required this.onYesPressed, @required this.onYesPressed,
@required this.onNoPressed,
}) : super(key: key); }) : super(key: key);
@override @override
@ -26,7 +24,9 @@ class AppYesNoDialog extends StatelessWidget {
), ),
FlatButton( FlatButton(
child: Text("No"), child: Text("No"),
onPressed: onNoPressed, onPressed: () {
Navigator.pop(context);
},
) )
], ],
); );

View File

@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/widgets/app_yes_no_dialog_widget.dart';
class DetailScreenFloatingActionButtons extends StatelessWidget {
final bool editMode;
final TimeProgress originalProgress, editedProgress;
final void Function() onEditProgress,
onSaveEditedProgress,
onCancelEditProgress,
onDeleteProgress;
DetailScreenFloatingActionButtons({
@required this.editMode,
@required this.originalProgress,
@required this.editedProgress,
@required this.onEditProgress,
@required this.onSaveEditedProgress,
@required this.onCancelEditProgress,
@required this.onDeleteProgress,
});
@override
Widget build(BuildContext context) {
final ThemeData appTheme = Theme.of(context);
void _onCancelEditTimeProgressBTN() {
if (originalProgress == editedProgress)
onCancelEditProgress();
else {
showDialog(
context: context,
builder: (_) => AppYesNoDialog(
titleText: "Cancel Editing of ${originalProgress.name}",
contentText:
"Are you sure that you want to discard the changes done to ${originalProgress.name}",
onYesPressed: () {
onCancelEditProgress();
Navigator.pop(context);
},
),
);
}
}
void _onDeleteTimeProgressBTN() {
showDialog(
context: context,
builder: (_) => AppYesNoDialog(
titleText: "Delete ${originalProgress.name}",
contentText: "Are you sure you want to delete this time progress?",
onYesPressed: onDeleteProgress,
),
);
}
return Row(
children: [
Expanded(
child: FloatingActionButton(
heroTag:
editMode ? "saveEditedTimeProgressBTN" : "editTimeProgressBTN",
child: editMode ? Icon(Icons.save) : Icon(Icons.edit),
backgroundColor: editMode ? Colors.green : appTheme.accentColor,
onPressed: editMode
? TimeProgress.isValid(editedProgress)
? onSaveEditedProgress
: null
: onEditProgress,
),
),
Expanded(
child: FloatingActionButton(
heroTag: editMode
? "cancelEditTimeProgressBTN"
: "deleteTimeProgressBTN",
child: editMode ? Icon(Icons.cancel) : Icon(Icons.delete),
backgroundColor: Colors.red,
onPressed: editMode
? _onCancelEditTimeProgressBTN
: _onDeleteTimeProgressBTN,
),
),
],
);
}
}

View File

@ -1,21 +0,0 @@
import 'package:flutter/material.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
class ProgressDetailCircularPercent extends StatelessWidget {
final double percentDone;
ProgressDetailCircularPercent({Key key, @required this.percentDone})
: super(key: key);
@override
Widget build(BuildContext context) {
return CircularPercentIndicator(
radius: 100,
lineWidth: 10,
percent: percentDone,
progressColor: Colors.green,
backgroundColor: Colors.red,
center: Text("${(percentDone * 100).floor()} %"),
);
}
}

View File

@ -1,44 +0,0 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_select_date_btn_widget.dart';
class ProgressDetailEditDatesRow extends StatelessWidget {
final DateTime startTime;
final DateTime endTime;
final void Function(DateTime) onStartTimeChanged;
final void Function(DateTime) onEndTimeChanged;
ProgressDetailEditDatesRow({
Key key,
@required this.startTime,
@required this.endTime,
@required this.onStartTimeChanged,
@required this.onEndTimeChanged,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
flex: 5,
child: ProgressDetailSelectDateButton(
leadingString: "Start Date:",
selectedDate: startTime,
onDateSelected: onStartTimeChanged,
),
),
Spacer(
flex: 1,
),
Expanded(
flex: 5,
child: ProgressDetailSelectDateButton(
leadingString: "End Date:",
selectedDate: endTime,
onDateSelected: onEndTimeChanged,
),
)
],
);
}
}

View File

@ -1,35 +0,0 @@
import 'package:flutter/material.dart';
class ProgressDetailFabEditingRow extends StatelessWidget {
final void Function() onSave;
final void Function() onCancelEdit;
ProgressDetailFabEditingRow({
Key key,
@required this.onSave,
@required this.onCancelEdit,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
child: FloatingActionButton(
heroTag: "saveEditedTimeProgressBTN",
child: Icon(Icons.save),
backgroundColor: Colors.green,
onPressed: this.onSave,
),
),
Expanded(
child: FloatingActionButton(
heroTag: "cancelEditTimeProgressBTN",
child: Icon(Icons.cancel),
backgroundColor: Colors.red,
onPressed: this.onCancelEdit,
))
],
);
}
}

View File

@ -1,33 +0,0 @@
import 'package:flutter/material.dart';
class ProgressDetailFabRow extends StatelessWidget {
final void Function() onEdit;
final void Function() onDelete;
ProgressDetailFabRow(
{Key key, @required this.onEdit, @required this.onDelete})
: super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
child: FloatingActionButton(
heroTag: "editTimeProgressBTN",
child: Icon(Icons.edit),
onPressed: onEdit,
),
),
Expanded(
child: FloatingActionButton(
heroTag: "deleteTimeProgressBTN",
child: Icon(Icons.delete),
backgroundColor: Colors.red,
onPressed: onDelete,
),
)
],
);
}
}

View File

@ -1,24 +0,0 @@
import 'package:flutter/material.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
class ProgressDetailLinearPercent extends StatelessWidget {
final TimeProgress timeProgress;
ProgressDetailLinearPercent({Key key, @required this.timeProgress})
: super(key: key);
@override
Widget build(BuildContext context) {
return LinearPercentIndicator(
padding: EdgeInsets.symmetric(horizontal: 15),
percent: this.timeProgress.percentDone(),
leading: Text("${this.timeProgress.daysBehind()} Days"),
center: Text("${(this.timeProgress.percentDone() * 100).floor()} %"),
trailing: Text("${this.timeProgress.daysLeft()} Days"),
progressColor: Colors.green,
backgroundColor: Colors.red,
lineHeight: 25,
);
}
}

View File

@ -1,34 +0,0 @@
import 'package:flutter/material.dart';
class ProgressDetailSelectDateButton extends StatelessWidget {
final String leadingString;
final DateTime selectedDate;
final void Function(DateTime) onDateSelected;
ProgressDetailSelectDateButton({
Key key,
@required this.leadingString,
@required this.selectedDate,
@required this.onDateSelected,
}) : super(key: key);
@override
Widget build(BuildContext context) {
ThemeData appTheme = Theme.of(context);
return FlatButton(
color: appTheme.accentColor,
child: Text(
"$leadingString ${selectedDate.toLocal().toString().split(" ")[0]}"),
onPressed: () async {
DateTime picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(selectedDate.year - 5),
lastDate: DateTime(selectedDate.year + 5),
);
onDateSelected(picked);
},
);
}
}