Cleaned up the repo
Singed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
This commit is contained in:
34
lib/ui/app_yes_no_dialog_widget.dart
Normal file
34
lib/ui/app_yes_no_dialog_widget.dart
Normal file
@ -0,0 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppYesNoDialog extends StatelessWidget {
|
||||
final String titleText;
|
||||
final String contentText;
|
||||
final void Function() onYesPressed;
|
||||
|
||||
AppYesNoDialog({
|
||||
Key key,
|
||||
@required this.titleText,
|
||||
@required this.contentText,
|
||||
@required this.onYesPressed,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(titleText),
|
||||
content: Text(contentText),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text("Yes"),
|
||||
onPressed: onYesPressed,
|
||||
),
|
||||
FlatButton(
|
||||
child: Text("No"),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
46
lib/ui/buttons/color_picker_btn.dart
Normal file
46
lib/ui/buttons/color_picker_btn.dart
Normal file
@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||
import 'package:time_progress_tracker/utils/helper_functions.dart';
|
||||
|
||||
class ColorPickerButton extends StatelessWidget {
|
||||
final String title, dialogTitle;
|
||||
final Color selectedColor;
|
||||
final void Function(Color) onColorPicked;
|
||||
|
||||
ColorPickerButton({
|
||||
@required this.title,
|
||||
@required this.dialogTitle,
|
||||
@required this.selectedColor,
|
||||
@required this.onColorPicked,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ThemeData appTheme = Theme.of(context);
|
||||
return TextButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text(dialogTitle),
|
||||
content: SingleChildScrollView(
|
||||
child: BlockPicker(
|
||||
pickerColor: selectedColor,
|
||||
onColorChanged: onColorPicked,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Text(title),
|
||||
style: TextButton.styleFrom(
|
||||
primary: useBrightBackground(selectedColor)
|
||||
? appTheme.primaryTextTheme.button.color
|
||||
: appTheme.textTheme.button.color,
|
||||
backgroundColor: selectedColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
18
lib/ui/buttons/create_progress_button.dart
Normal file
18
lib/ui/buttons/create_progress_button.dart
Normal file
@ -0,0 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:time_progress_tracker/ui/screens/progress_creation_screen.dart';
|
||||
|
||||
class CreateProgressButton extends StatelessWidget {
|
||||
final String _heroTag = "createProgressBTN";
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
void _onButtonPressed() =>
|
||||
Navigator.pushNamed(context, ProgressCreationScreen.routeName);
|
||||
|
||||
return FloatingActionButton(
|
||||
heroTag: _heroTag,
|
||||
child: Icon(Icons.add),
|
||||
onPressed: _onButtonPressed,
|
||||
);
|
||||
}
|
||||
}
|
36
lib/ui/buttons/date_picker_btn.dart
Normal file
36
lib/ui/buttons/date_picker_btn.dart
Normal file
@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DatePickerBtn extends StatelessWidget {
|
||||
final String leadingString;
|
||||
final DateTime pickedDate;
|
||||
final void Function(DateTime) onDatePicked;
|
||||
|
||||
DatePickerBtn({
|
||||
@required this.leadingString,
|
||||
@required this.pickedDate,
|
||||
@required this.onDatePicked,
|
||||
}) : super();
|
||||
|
||||
void _onButtonPressed(BuildContext context) async {
|
||||
onDatePicked(await showDatePicker(
|
||||
context: context,
|
||||
initialDate: pickedDate,
|
||||
firstDate: DateTime(1900),
|
||||
lastDate: DateTime(2100),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ThemeData appTheme = Theme.of(context);
|
||||
return TextButton(
|
||||
onPressed: () => _onButtonPressed(context),
|
||||
child: Text(
|
||||
"$leadingString ${pickedDate.toLocal().toString().split(" ")[0]}"),
|
||||
style: TextButton.styleFrom(
|
||||
primary: appTheme.primaryTextTheme.button.color,
|
||||
backgroundColor: appTheme.accentColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
47
lib/ui/buttons/select_duration_btn.dart
Normal file
47
lib/ui/buttons/select_duration_btn.dart
Normal file
@ -0,0 +1,47 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_picker/flutter_picker.dart';
|
||||
|
||||
class SelectDurationBtn extends StatelessWidget {
|
||||
final Duration duration;
|
||||
final void Function(Duration) updateDuration;
|
||||
|
||||
SelectDurationBtn({
|
||||
@required this.duration,
|
||||
@required this.updateDuration,
|
||||
});
|
||||
|
||||
void _onPickerConfirm(Picker picker, List<int> values) {
|
||||
int years = values[0], months = values[1], days = values[2];
|
||||
days = (years * 365) + (months * 31) + days;
|
||||
Duration newDuration = Duration(days: days);
|
||||
updateDuration(newDuration);
|
||||
}
|
||||
|
||||
void _onButtonPressed(BuildContext context, ThemeData appTheme) => Picker(
|
||||
adapter: NumberPickerAdapter(data: [
|
||||
const NumberPickerColumn(begin: 0, end: 999, suffix: Text(" Y")),
|
||||
const NumberPickerColumn(begin: 0, end: 11, suffix: Text(" M")),
|
||||
const NumberPickerColumn(begin: 0, end: 31, suffix: Text(" D")),
|
||||
]),
|
||||
hideHeader: false,
|
||||
title: const Text("Default Duration"),
|
||||
selectedTextStyle: TextStyle(color: appTheme.accentColor),
|
||||
onConfirm: _onPickerConfirm)
|
||||
.showModal(context);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ThemeData appTheme = Theme.of(context);
|
||||
|
||||
int years = duration.inDays ~/ 365;
|
||||
int months = (duration.inDays - (365 * years)) ~/ 30;
|
||||
int days = duration.inDays - (365 * years) - (30 * months);
|
||||
return TextButton(
|
||||
onPressed: () => _onButtonPressed(context, appTheme),
|
||||
child: Text("$years Years $months Months $days Days"),
|
||||
style: TextButton.styleFrom(
|
||||
primary: appTheme.primaryTextTheme.button.color,
|
||||
backgroundColor: appTheme.accentColor,
|
||||
));
|
||||
}
|
||||
}
|
88
lib/ui/detail_screen_floating_action_buttons.dart
Normal file
88
lib/ui/detail_screen_floating_action_buttons.dart
Normal file
@ -0,0 +1,88 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:time_progress_tracker/models/time_progress.dart';
|
||||
import 'package:time_progress_tracker/ui/app_yes_no_dialog_widget.dart';
|
||||
|
||||
class DetailScreenFloatingActionButtons extends StatelessWidget {
|
||||
final bool editMode, isEditedProgressValid;
|
||||
final TimeProgress originalProgress, editedProgress;
|
||||
final void Function() onEditProgress,
|
||||
onSaveEditedProgress,
|
||||
onCancelEditProgress,
|
||||
onDeleteProgress;
|
||||
|
||||
DetailScreenFloatingActionButtons({
|
||||
@required this.editMode,
|
||||
@required this.originalProgress,
|
||||
@required this.editedProgress,
|
||||
@required this.isEditedProgressValid,
|
||||
@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
|
||||
? isEditedProgressValid
|
||||
? 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
124
lib/ui/progress/progress_editor_widget.dart
Normal file
124
lib/ui/progress/progress_editor_widget.dart
Normal file
@ -0,0 +1,124 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:time_progress_tracker/models/time_progress.dart';
|
||||
import 'package:time_progress_tracker/ui/buttons/date_picker_btn.dart';
|
||||
|
||||
class ProgressEditorWidget extends StatefulWidget {
|
||||
final TimeProgress timeProgress;
|
||||
final Function(TimeProgress, bool) onTimeProgressChanged;
|
||||
|
||||
ProgressEditorWidget({
|
||||
@required this.timeProgress,
|
||||
@required this.onTimeProgressChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _ProgressEditorWidgetState();
|
||||
}
|
||||
}
|
||||
|
||||
class _ProgressEditorWidgetState extends State<ProgressEditorWidget> {
|
||||
final _nameTextController = TextEditingController();
|
||||
bool _validName = true, _validDate = true;
|
||||
|
||||
void _onNameChanged() {
|
||||
TimeProgress newProgress =
|
||||
widget.timeProgress.copyWith(name: _nameTextController.text);
|
||||
widget.onTimeProgressChanged(
|
||||
newProgress, TimeProgress.isValid(newProgress));
|
||||
setState(() {
|
||||
_validName = TimeProgress.isNameValid(newProgress.name);
|
||||
});
|
||||
}
|
||||
|
||||
void _onStartDateChanged(DateTime newStartDate) {
|
||||
TimeProgress newProgress =
|
||||
widget.timeProgress.copyWith(startTime: newStartDate);
|
||||
widget.onTimeProgressChanged(
|
||||
newProgress, TimeProgress.isValid(newProgress));
|
||||
setState(() {
|
||||
_validDate =
|
||||
TimeProgress.areTimesValid(newStartDate, newProgress.endTime);
|
||||
});
|
||||
}
|
||||
|
||||
void _onEndDateChanged(DateTime newEndDate) {
|
||||
TimeProgress newProgress =
|
||||
widget.timeProgress.copyWith(endTime: newEndDate);
|
||||
widget.onTimeProgressChanged(
|
||||
newProgress, TimeProgress.isValid(newProgress));
|
||||
setState(() {
|
||||
_validDate =
|
||||
TimeProgress.areTimesValid(newProgress.startTime, newEndDate);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_nameTextController.text = widget.timeProgress.name;
|
||||
_nameTextController.addListener(_onNameChanged);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> columnChildren = [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _nameTextController,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: "Progress Name",
|
||||
errorText: _validName
|
||||
? null
|
||||
: "The Name need to have at least 3 and at max 20 symbols.",
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 5),
|
||||
child: DatePickerBtn(
|
||||
leadingString: "Start Date:",
|
||||
pickedDate: widget.timeProgress.startTime,
|
||||
onDatePicked: _onStartDateChanged,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 5),
|
||||
child: DatePickerBtn(
|
||||
leadingString: "End Date:",
|
||||
pickedDate: widget.timeProgress.endTime,
|
||||
onDatePicked: _onEndDateChanged,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
];
|
||||
|
||||
if (!_validDate)
|
||||
columnChildren.add(
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Invalid Dates. The Start Date has to be before the End Date",
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return Container(
|
||||
child: Column(
|
||||
children: columnChildren,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
74
lib/ui/progress/progress_list_item.dart
Normal file
74
lib/ui/progress/progress_list_item.dart
Normal file
@ -0,0 +1,74 @@
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
||||
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||
import 'package:time_progress_tracker/models/time_progress.dart';
|
||||
import 'package:time_progress_tracker/ui/screens/progress_detail_screen.dart';
|
||||
|
||||
class ProgressListTileStrings {
|
||||
static String percentString(TimeProgress tp) =>
|
||||
"${(tp.percentDone() * 100).floorToDouble()} %";
|
||||
|
||||
static String startsInDaysString(TimeProgress tp) =>
|
||||
"Starts in ${tp.daysTillStart()} Days.";
|
||||
|
||||
static String endedDaysAgoString(TimeProgress tp) =>
|
||||
"Ended ${tp.daysSinceEnd()} Days ago.";
|
||||
}
|
||||
|
||||
class ProgressListItem extends StatelessWidget {
|
||||
final TimeProgress timeProgress;
|
||||
final Color doneColor, leftColor;
|
||||
|
||||
ProgressListItem({
|
||||
@required this.timeProgress,
|
||||
@required this.doneColor,
|
||||
@required this.leftColor,
|
||||
});
|
||||
|
||||
Widget _renderSubtitle(BuildContext context) {
|
||||
if (!timeProgress.hasStarted())
|
||||
return PlatformText(ProgressListTileStrings.startsInDaysString(timeProgress));
|
||||
if (timeProgress.hasEnded())
|
||||
return PlatformText(ProgressListTileStrings.endedDaysAgoString(timeProgress));
|
||||
return LinearPercentIndicator(
|
||||
center: PlatformText(ProgressListTileStrings.percentString(timeProgress)),
|
||||
percent: timeProgress.percentDone(),
|
||||
progressColor: doneColor,
|
||||
backgroundColor: leftColor,
|
||||
lineHeight: 20,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
void _onTileTap() =>
|
||||
Navigator.pushNamed(context, ProgressDetailScreen.routeName,
|
||||
arguments: ProgressDetailScreenArguments(timeProgress.id));
|
||||
Text titleText = Text(timeProgress.name);
|
||||
|
||||
if (Platform.isIOS)
|
||||
return CupertinoButton(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: EdgeInsets.fromLTRB(15, 15, 5, 5),
|
||||
child: Column(
|
||||
children: [
|
||||
titleText,
|
||||
_renderSubtitle(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
onPressed: _onTileTap);
|
||||
return ListTile(
|
||||
title: titleText,
|
||||
subtitle: _renderSubtitle(context),
|
||||
onTap: _onTileTap,
|
||||
);
|
||||
}
|
||||
}
|
40
lib/ui/progress/progress_list_view.dart
Normal file
40
lib/ui/progress/progress_list_view.dart
Normal file
@ -0,0 +1,40 @@
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:time_progress_tracker/models/time_progress.dart';
|
||||
import 'package:time_progress_tracker/ui/progress/progress_list_item.dart';
|
||||
|
||||
class ProgressListView extends StatelessWidget {
|
||||
final List<TimeProgress> timeProgressList;
|
||||
final Color doneColor, leftColor;
|
||||
|
||||
ProgressListView({
|
||||
@required this.timeProgressList,
|
||||
@required this.doneColor,
|
||||
@required this.leftColor,
|
||||
});
|
||||
|
||||
Widget _renderListTile(TimeProgress tp) {
|
||||
ProgressListItem listTile = ProgressListItem(
|
||||
timeProgress: tp, doneColor: doneColor, leftColor: leftColor);
|
||||
if (Platform.isIOS) return listTile;
|
||||
return Card(
|
||||
child: listTile,
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _renderListViewChildren() {
|
||||
return timeProgressList
|
||||
.map((e) => _renderListTile(e))
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView(
|
||||
padding: EdgeInsets.all(8),
|
||||
children: _renderListViewChildren(),
|
||||
);
|
||||
}
|
||||
}
|
64
lib/ui/progress/progress_view_widget.dart
Normal file
64
lib/ui/progress/progress_view_widget.dart
Normal file
@ -0,0 +1,64 @@
|
||||
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;
|
||||
final Color doneColor;
|
||||
final Color leftColor;
|
||||
|
||||
ProgressViewWidget({
|
||||
@required this.timeProgress,
|
||||
@required this.doneColor,
|
||||
@required this.leftColor,
|
||||
});
|
||||
|
||||
@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: doneColor,
|
||||
backgroundColor: leftColor,
|
||||
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()} %",
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
trailing: Text("${timeProgress.daysLeft()} Days"),
|
||||
progressColor: doneColor,
|
||||
backgroundColor: leftColor,
|
||||
lineHeight: 25,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
32
lib/ui/screens/active_time_progresses_screen.dart
Normal file
32
lib/ui/screens/active_time_progresses_screen.dart
Normal file
@ -0,0 +1,32 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
||||
import 'package:time_progress_tracker/redux/store_connectors/settings_store_connector.dart';
|
||||
import 'package:time_progress_tracker/redux/store_connectors/time_progress_list_store_connector.dart';
|
||||
import 'package:time_progress_tracker/utils/helper_functions.dart';
|
||||
import 'package:time_progress_tracker/models/time_progress.dart';
|
||||
import 'package:time_progress_tracker/ui/progress/progress_list_view.dart';
|
||||
|
||||
class ActiveTimeProgressesScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SettingsStoreConnector(loadedBuilder: (context, settingsVm) {
|
||||
return TimeProgressListStoreConnector(loadedBuilder: (context, tpListVm) {
|
||||
List<TimeProgress> activeTpList =
|
||||
selectActiveProgresses(tpListVm.tpList);
|
||||
if (activeTpList.length < 1)
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: PlatformText(
|
||||
"You don't have any active time progress, that are tracked."),
|
||||
),
|
||||
);
|
||||
return ProgressListView(
|
||||
timeProgressList: activeTpList,
|
||||
doneColor: settingsVm.appSettings.doneColor,
|
||||
leftColor: settingsVm.appSettings.leftColor,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
85
lib/ui/screens/dashboard_screen.dart
Normal file
85
lib/ui/screens/dashboard_screen.dart
Normal file
@ -0,0 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
||||
import 'package:time_progress_tracker/ui/screens/active_time_progresses_screen.dart';
|
||||
import 'package:time_progress_tracker/ui/screens/inactive_time_progresses_screen.dart';
|
||||
import 'package:time_progress_tracker/ui/screens/settings_screen.dart';
|
||||
import 'package:time_progress_tracker/utils/color_utils.dart';
|
||||
import 'package:time_progress_tracker/utils/constants.dart';
|
||||
|
||||
class DashboardScreen extends StatefulWidget {
|
||||
static const routeName = "/dashboard";
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _DashboardScreenState();
|
||||
}
|
||||
|
||||
class _DashboardScreenState extends State<DashboardScreen> {
|
||||
int _tabSelectedIndex = 0;
|
||||
String title = txtActiveProgressesScreen;
|
||||
|
||||
Widget _renderTabScreen(int tabIndex) {
|
||||
switch (tabIndex) {
|
||||
case 1:
|
||||
return InactiveTimeProgressesScreen();
|
||||
case 2:
|
||||
return SettingsScreen();
|
||||
default:
|
||||
return ActiveTimeProgressesScreen();
|
||||
}
|
||||
}
|
||||
|
||||
String getScreenTitle(int tabIndex) {
|
||||
switch (tabIndex) {
|
||||
case 1:
|
||||
return txtInactiveProgressesScreen;
|
||||
case 2:
|
||||
return txtSettingsScreen;
|
||||
default:
|
||||
return txtActiveProgressesScreen;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PlatformScaffold(
|
||||
appBar: PlatformAppBar(
|
||||
title: Text(
|
||||
title,
|
||||
style: toolbarTextStyle,
|
||||
),
|
||||
cupertino: (_, __) => CupertinoNavigationBarData(
|
||||
transitionBetweenRoutes: false,
|
||||
),
|
||||
),
|
||||
material: (_, __) => MaterialScaffoldData(),
|
||||
body: _renderTabScreen(_tabSelectedIndex),
|
||||
bottomNavBar: PlatformNavBar(
|
||||
currentIndex: _tabSelectedIndex,
|
||||
itemChanged: (index) {
|
||||
setState(() {
|
||||
_tabSelectedIndex = index;
|
||||
title = getScreenTitle(index);
|
||||
});
|
||||
},
|
||||
backgroundColor: bottomTabsBackground,
|
||||
items: [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.alarm, color: Colors.grey),
|
||||
label: txtActiveProgressesScreen,
|
||||
activeIcon: Icon(Icons.alarm, color: Colors.white),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.alarm_off, color: Colors.grey),
|
||||
label: txtInactiveProgressesScreen,
|
||||
activeIcon: Icon(Icons.alarm_off, color: Colors.white),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.settings, color: Colors.grey),
|
||||
label: txtSettingsScreen,
|
||||
activeIcon: Icon(Icons.settings, color: Colors.white),
|
||||
)
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
32
lib/ui/screens/inactive_time_progresses_screen.dart
Normal file
32
lib/ui/screens/inactive_time_progresses_screen.dart
Normal file
@ -0,0 +1,32 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
||||
import 'package:time_progress_tracker/redux/store_connectors/settings_store_connector.dart';
|
||||
import 'package:time_progress_tracker/redux/store_connectors/time_progress_list_store_connector.dart';
|
||||
import 'package:time_progress_tracker/utils/helper_functions.dart';
|
||||
import 'package:time_progress_tracker/models/time_progress.dart';
|
||||
import 'package:time_progress_tracker/ui/progress/progress_list_view.dart';
|
||||
|
||||
class InactiveTimeProgressesScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SettingsStoreConnector(loadedBuilder: (context, settingsVm) {
|
||||
return TimeProgressListStoreConnector(loadedBuilder: (context, tpListVm) {
|
||||
List<TimeProgress> activeTpList =
|
||||
selectInactiveProgresses(tpListVm.tpList);
|
||||
if (activeTpList.length < 1)
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: PlatformText(
|
||||
"You don't have any inactive time progress, that are tracked."),
|
||||
),
|
||||
);
|
||||
return ProgressListView(
|
||||
timeProgressList: activeTpList,
|
||||
doneColor: settingsVm.appSettings.doneColor,
|
||||
leftColor: settingsVm.appSettings.leftColor,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
117
lib/ui/screens/progress_creation_screen.dart
Normal file
117
lib/ui/screens/progress_creation_screen.dart
Normal file
@ -0,0 +1,117 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_redux/flutter_redux.dart';
|
||||
import 'package:redux/redux.dart';
|
||||
import 'package:time_progress_tracker/models/app_settings.dart';
|
||||
import 'package:time_progress_tracker/redux/actions/time_progress_actions.dart';
|
||||
import 'package:time_progress_tracker/redux/app_state.dart';
|
||||
import 'package:time_progress_tracker/models/time_progress.dart';
|
||||
import 'package:time_progress_tracker/redux/redux_selectors.dart';
|
||||
import 'package:time_progress_tracker/utils/helper_functions.dart';
|
||||
import 'package:time_progress_tracker/ui/progress/progress_editor_widget.dart';
|
||||
|
||||
class ProgressCreationScreen extends StatefulWidget {
|
||||
static const routeName = "/create-progress";
|
||||
static const title = "Create Time Progress";
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _ProgressCreationScreenState();
|
||||
}
|
||||
}
|
||||
|
||||
class _ProgressCreationScreenState extends State<ProgressCreationScreen> {
|
||||
TimeProgress timeProgressToCreate;
|
||||
bool _isProgressValid = false;
|
||||
|
||||
void initTimeProgress(TimeProgress timeProgress) {
|
||||
if (timeProgressToCreate == null)
|
||||
setState(() {
|
||||
timeProgressToCreate = timeProgress;
|
||||
});
|
||||
}
|
||||
|
||||
void onTimeProgressChanged(
|
||||
TimeProgress newTimeProgress, bool isNewProgressValid) {
|
||||
setState(() {
|
||||
timeProgressToCreate = newTimeProgress;
|
||||
_isProgressValid = isNewProgressValid;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(ProgressCreationScreen.title),
|
||||
),
|
||||
body: Container(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: StoreConnector<AppState, _ViewModel>(
|
||||
onInit: loadSettingsIfUnloaded,
|
||||
converter: (store) => _ViewModel.create(store),
|
||||
builder: (context, _ViewModel viewModel) {
|
||||
initTimeProgress(viewModel.defaultDurationProgress);
|
||||
return ProgressEditorWidget(
|
||||
timeProgress: timeProgressToCreate,
|
||||
onTimeProgressChanged: onTimeProgressChanged,
|
||||
);
|
||||
}),
|
||||
),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||
floatingActionButton: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: StoreConnector<AppState, _ViewModel>(
|
||||
onInit: loadSettingsIfUnloaded,
|
||||
converter: (store) => _ViewModel.create(store),
|
||||
builder: (context, _ViewModel vm) => FloatingActionButton(
|
||||
heroTag: "createTimeProgressBTN",
|
||||
child: Icon(Icons.save),
|
||||
onPressed: _isProgressValid
|
||||
? () {
|
||||
vm.onAddTimeProgress(timeProgressToCreate);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: FloatingActionButton(
|
||||
heroTag: "cancelTimeProgressCreationBTN",
|
||||
child: Icon(Icons.cancel),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ViewModel {
|
||||
final TimeProgress defaultDurationProgress;
|
||||
final void Function(TimeProgress) onAddTimeProgress;
|
||||
|
||||
_ViewModel({
|
||||
@required this.defaultDurationProgress,
|
||||
@required this.onAddTimeProgress,
|
||||
});
|
||||
|
||||
factory _ViewModel.create(Store<AppState> store) {
|
||||
AppSettings settings = appSettingsSelector(store.state);
|
||||
|
||||
_onAddTimeProgress(TimeProgress tp) {
|
||||
if (TimeProgress.isValid(tp)) store.dispatch(AddTimeProgressAction(tp));
|
||||
}
|
||||
|
||||
return _ViewModel(
|
||||
defaultDurationProgress:
|
||||
TimeProgress.defaultFromDuration(settings.duration),
|
||||
onAddTimeProgress: _onAddTimeProgress,
|
||||
);
|
||||
}
|
||||
}
|
129
lib/ui/screens/progress_detail_screen.dart
Normal file
129
lib/ui/screens/progress_detail_screen.dart
Normal file
@ -0,0 +1,129 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:time_progress_tracker/models/time_progress.dart';
|
||||
import 'package:time_progress_tracker/redux/store_connectors/settings_store_connector.dart';
|
||||
import 'package:time_progress_tracker/redux/store_connectors/time_progress_store_connector.dart';
|
||||
import 'package:time_progress_tracker/ui/screens/dashboard_screen.dart';
|
||||
import 'package:time_progress_tracker/ui/detail_screen_floating_action_buttons.dart';
|
||||
import 'package:time_progress_tracker/ui/progress/progress_editor_widget.dart';
|
||||
import 'package:time_progress_tracker/ui/progress/progress_view_widget.dart';
|
||||
|
||||
class ProgressDetailScreenArguments {
|
||||
final String id;
|
||||
|
||||
ProgressDetailScreenArguments(this.id);
|
||||
}
|
||||
|
||||
class ProgressDetailScreen extends StatefulWidget {
|
||||
static const routeName = "/progress";
|
||||
static const title = "Progress View";
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _ProgressDetailScreenState();
|
||||
}
|
||||
}
|
||||
|
||||
class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
|
||||
bool _editMode = false, _isEditedProgressValid = false;
|
||||
TimeProgress _editedProgress, _originalProgress;
|
||||
|
||||
void _initEditedProgress(TimeProgress tp) {
|
||||
if (_editedProgress == null) {
|
||||
_editedProgress = tp;
|
||||
_originalProgress = tp;
|
||||
}
|
||||
}
|
||||
|
||||
void _onEditedProgressChanged(
|
||||
TimeProgress newProgress, bool isNewProgressValid) {
|
||||
setState(() {
|
||||
_editedProgress = newProgress;
|
||||
_isEditedProgressValid = isNewProgressValid;
|
||||
});
|
||||
}
|
||||
|
||||
void _switchEditMode(bool newMode) {
|
||||
setState(() {
|
||||
_editMode = newMode;
|
||||
});
|
||||
}
|
||||
|
||||
void _cancelEditMode() {
|
||||
setState(() {
|
||||
_editMode = false;
|
||||
_editedProgress = _originalProgress;
|
||||
});
|
||||
}
|
||||
|
||||
List<Widget> _renderColumnChildren(
|
||||
SettingsViewModel settingsVm, TimeProgressViewModel tpVm) {
|
||||
List<Widget> columnChildren = [
|
||||
Expanded(
|
||||
child: ProgressViewWidget(
|
||||
timeProgress: _editMode ? _editedProgress : tpVm.tp,
|
||||
doneColor: settingsVm.appSettings.doneColor,
|
||||
leftColor: settingsVm.appSettings.leftColor,
|
||||
))
|
||||
];
|
||||
if (_editMode)
|
||||
columnChildren.add(Expanded(
|
||||
child: ProgressEditorWidget(
|
||||
timeProgress: _editedProgress,
|
||||
onTimeProgressChanged: _onEditedProgressChanged,
|
||||
)));
|
||||
return columnChildren;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ProgressDetailScreenArguments args =
|
||||
ModalRoute.of(context).settings.arguments;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(ProgressDetailScreen.title),
|
||||
),
|
||||
body: SettingsStoreConnector(
|
||||
loadedBuilder: (context, settingsVm) {
|
||||
return TimeProgressStoreConnector(
|
||||
timeProgressId: args.id,
|
||||
loadedBuilder: (context, tpVm) {
|
||||
_initEditedProgress(tpVm.tp);
|
||||
return Container(
|
||||
margin: EdgeInsets.all(8),
|
||||
child: Column(
|
||||
children: _renderColumnChildren(settingsVm, tpVm),
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||
floatingActionButton: TimeProgressStoreConnector(
|
||||
timeProgressId: args.id,
|
||||
loadedBuilder: (context, tpVm) {
|
||||
void _saveEditedProgress() {
|
||||
tpVm.updateTimeProgress(_editedProgress);
|
||||
_switchEditMode(false);
|
||||
}
|
||||
|
||||
void _deleteTimeProgress() {
|
||||
tpVm.deleteTimeProgress();
|
||||
Navigator.popUntil(
|
||||
context, ModalRoute.withName(DashboardScreen.routeName));
|
||||
}
|
||||
|
||||
return DetailScreenFloatingActionButtons(
|
||||
editMode: _editMode,
|
||||
originalProgress: tpVm.tp,
|
||||
editedProgress: _editedProgress,
|
||||
isEditedProgressValid: _isEditedProgressValid,
|
||||
onEditProgress: () => _switchEditMode(true),
|
||||
onSaveEditedProgress: _saveEditedProgress,
|
||||
onCancelEditProgress: _cancelEditMode,
|
||||
onDeleteProgress: _deleteTimeProgress);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
89
lib/ui/screens/settings_screen.dart
Normal file
89
lib/ui/screens/settings_screen.dart
Normal file
@ -0,0 +1,89 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
||||
import 'package:time_progress_tracker/app.dart';
|
||||
import 'package:time_progress_tracker/redux/store_connectors/settings_store_connector.dart';
|
||||
import 'package:time_progress_tracker/ui/buttons/color_picker_btn.dart';
|
||||
import 'package:time_progress_tracker/ui/settings/duration_settings_widget.dart';
|
||||
|
||||
class SettingsScreen extends StatelessWidget {
|
||||
Widget _renderColorSettings(
|
||||
BuildContext context, SettingsViewModel settingsVm) {
|
||||
ThemeData appTheme = Theme.of(context);
|
||||
return Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: PlatformText(
|
||||
"Color Settings",
|
||||
style: appTheme.primaryTextTheme.caption,
|
||||
)),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 5),
|
||||
child: ColorPickerButton(
|
||||
title: "Done Color",
|
||||
dialogTitle: "Select Done Color",
|
||||
selectedColor: settingsVm.appSettings.doneColor,
|
||||
onColorPicked: settingsVm.updateDoneColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 5),
|
||||
child: ColorPickerButton(
|
||||
title: "Left Color",
|
||||
dialogTitle: "Select Left Color",
|
||||
selectedColor: settingsVm.appSettings.leftColor,
|
||||
onColorPicked: settingsVm.updateLeftColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SettingsStoreConnector(loadedBuilder: (context, settingsVm) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _renderColorSettings(context, settingsVm),
|
||||
),
|
||||
Expanded(
|
||||
child: DurationSettingsWidget(
|
||||
duration: settingsVm.appSettings.duration,
|
||||
updateDuration: settingsVm.updateDuration,
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Expanded(
|
||||
child: PlatformButton(
|
||||
onPressed: () {
|
||||
showAboutDialog(
|
||||
context: context,
|
||||
applicationName: TimeProgressTrackerApp.name,
|
||||
applicationVersion: "Beta",
|
||||
applicationLegalese:
|
||||
'\u00a9Andreas Fahrecker 2020-2021');
|
||||
},
|
||||
child: Text("About"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
56
lib/ui/settings/color_settings_widget.dart
Normal file
56
lib/ui/settings/color_settings_widget.dart
Normal file
@ -0,0 +1,56 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:time_progress_tracker/ui/buttons/color_picker_btn.dart';
|
||||
|
||||
class ColorSettingsWidget extends StatelessWidget {
|
||||
final Color doneColor, leftColor;
|
||||
final void Function(Color) updateDoneColor, updateLeftColor;
|
||||
|
||||
ColorSettingsWidget({
|
||||
@required this.doneColor,
|
||||
@required this.leftColor,
|
||||
@required this.updateDoneColor,
|
||||
@required this.updateLeftColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ThemeData appTheme = Theme.of(context);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Color Settings",
|
||||
style: appTheme.textTheme.headline6,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 5),
|
||||
child: ColorPickerButton(
|
||||
title: "Done Color",
|
||||
dialogTitle: "Select Done Color",
|
||||
selectedColor: doneColor,
|
||||
onColorPicked: updateDoneColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 5),
|
||||
child: ColorPickerButton(
|
||||
title: "Left Color",
|
||||
dialogTitle: "Select Left Color",
|
||||
selectedColor: leftColor,
|
||||
onColorPicked: updateLeftColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
41
lib/ui/settings/duration_settings_widget.dart
Normal file
41
lib/ui/settings/duration_settings_widget.dart
Normal file
@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:time_progress_tracker/ui/buttons/select_duration_btn.dart';
|
||||
|
||||
class DurationSettingsWidget extends StatelessWidget {
|
||||
final Duration duration;
|
||||
final void Function(Duration) updateDuration;
|
||||
|
||||
DurationSettingsWidget({
|
||||
@required this.duration,
|
||||
@required this.updateDuration,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ThemeData appTheme = Theme.of(context);
|
||||
|
||||
int years = duration.inDays ~/ 365;
|
||||
int months = (duration.inDays - (365 * years)) ~/ 30;
|
||||
int days = duration.inDays - (365 * years) - (30 * months);
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Duration Settings",
|
||||
style: appTheme.textTheme.headline6,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SelectDurationBtn(
|
||||
duration: duration,
|
||||
updateDuration: updateDuration,
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user