Feature/code cleanup (#9)

* Implemented hasSettingsLoaded reducer
* Added Padding to Progress List View
* Created Settings and Time Progress List Store Connector
* Rewritten Home Active Tab
* Fixed missing onTap in Progress List Tile
* Started using new Store Connectors in Inactive and Settings Tab
* Created Time Progress Store Connector
* Rewritten ProgressDetailScreen with new Store Connectors
* Rewritten DatePickerBtn with TextButton
* Deleted unused widget
* Changed Foreground Color behaviour in ColorPicker BTN
* Created Select Duration Button
* Rewritten Duration Setting Widget
* Updated Version Number

Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
This commit is contained in:
Andreas Fahrecker 2021-03-08 20:48:45 +01:00 committed by GitHub
parent fc35476503
commit 40bdcc44f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 450 additions and 319 deletions

17
lib/helper_functions.dart Normal file
View File

@ -0,0 +1,17 @@
import 'dart:ui';
import 'package:time_progress_tracker/models/time_progress.dart';
TimeProgress selectProgressById(List<TimeProgress> tpList, String id) =>
tpList.firstWhere((tp) => tp.id == id, orElse: null);
List<TimeProgress> selectActiveProgresses(List<TimeProgress> tpList) =>
tpList.where((tp) => tp.hasStarted() && !tp.hasEnded()).toList();
List<TimeProgress> selectInactiveProgresses(List<TimeProgress> tpList) =>
tpList.where((tp) => !tp.hasStarted() || tp.hasEnded()).toList();
bool useBrightBackground(Color bC) {
double yiq = ((bC.red * 299) + (bC.green * 587) + (bC.blue * 114)) / 1000;
return yiq >= 186 || (bC.red == 0 && bC.green == 0 && bC.blue == 0);
}

View File

@ -7,7 +7,10 @@ import 'package:time_progress_tracker/reducers/time_progress_list_reducer.dart';
AppState appStateReducer(AppState state, dynamic action) { AppState appStateReducer(AppState state, dynamic action) {
return AppState( return AppState(
hasProgressesLoaded: hasLoadedReducer(state.hasProgressesLoaded, action), hasSettingsLoaded:
hasSettingsLoadedReducer(state.hasSettingsLoaded, action),
hasProgressesLoaded:
hasProgressesLoadedReducer(state.hasProgressesLoaded, action),
timeProgressList: timeProgressListReducer(state.timeProgressList, action), timeProgressList: timeProgressListReducer(state.timeProgressList, action),
appSettings: appSettingsReducers(state.appSettings, action), appSettings: appSettingsReducers(state.appSettings, action),
); );

View File

@ -1,15 +1,28 @@
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';
final hasLoadedReducer = combineReducers<bool>([ final hasProgressesLoadedReducer = combineReducers<bool>([
TypedReducer<bool, TimeProgressListLoadedAction>(_setLoaded), TypedReducer<bool, TimeProgressListLoadedAction>(_setProgressesLoaded),
TypedReducer<bool, TimeProgressListNotLoadedAction>(_setUnloaded) TypedReducer<bool, TimeProgressListNotLoadedAction>(_setProgressesUnloaded)
]); ]);
bool _setLoaded(bool hasLoaded, TimeProgressListLoadedAction action) { bool _setProgressesLoaded(bool hasLoaded, TimeProgressListLoadedAction action) {
return true; return true;
} }
bool _setUnloaded(bool hasLoaded, TimeProgressListNotLoadedAction action) { bool _setProgressesUnloaded(bool hasLoaded, TimeProgressListNotLoadedAction action) {
return false; return false;
} }
final hasSettingsLoadedReducer = combineReducers<bool>([
TypedReducer<bool, AppSettingsLoadedActions>(_setSettingsLoaded),
TypedReducer<bool, AppSettingsNotLoadedAction>(_setSettingsUnloaded)
]);
bool _setSettingsLoaded(bool hasLoaded, AppSettingsLoadedActions action) {
return true;
}
bool _setSettingsUnloaded(bool hasLoaded, AppSettingsNotLoadedAction action) {
return false;
}

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:time_progress_tracker/screens/progress_creation_screen.dart'; import 'package:time_progress_tracker/widgets/buttons/create_progress_button.dart';
import 'package:time_progress_tracker/widgets/home/home_bottom_navbar.dart'; import 'package:time_progress_tracker/widgets/home/home_bottom_navbar.dart';
import 'package:time_progress_tracker/widgets/home/tabs/home_active_progresses_tab.dart'; import 'package:time_progress_tracker/widgets/home/tabs/home_active_progresses_tab.dart';
import 'package:time_progress_tracker/widgets/home/tabs/home_inactive_progresses_tab.dart'; import 'package:time_progress_tracker/widgets/home/tabs/home_inactive_progresses_tab.dart';
@ -37,15 +37,7 @@ class _HomeScreenState extends State<HomeScreen> {
), ),
body: _children[_currentIndex], body: _children[_currentIndex],
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: _currentIndex != 2 floatingActionButton: _currentIndex != 2 ? CreateProgressButton() : null,
? FloatingActionButton(
heroTag: "createProgressBTN",
child: Icon(Icons.add),
onPressed: () {
Navigator.pushNamed(context, ProgressCreationScreen.routeName);
},
)
: null,
bottomNavigationBar: HomeBottomNavBar( bottomNavigationBar: HomeBottomNavBar(
currentIndex: _currentIndex, currentIndex: _currentIndex,
onTap: onBottomTabTapped, onTap: onBottomTabTapped,

View File

@ -1,15 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/app_settings.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/widgets/detail_screen_floating_action_buttons.dart'; import 'package:time_progress_tracker/widgets/detail_screen_floating_action_buttons.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';
import 'package:time_progress_tracker/widgets/store_connectors/settings_store_connector.dart';
import 'package:time_progress_tracker/widgets/store_connectors/time_progress_store_connector.dart';
class ProgressDetailScreenArguments { class ProgressDetailScreenArguments {
final String id; final String id;
@ -31,6 +27,13 @@ class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
bool _editMode = false, _isEditedProgressValid = false; bool _editMode = false, _isEditedProgressValid = false;
TimeProgress _editedProgress, _originalProgress; TimeProgress _editedProgress, _originalProgress;
void _initEditedProgress(TimeProgress tp) {
if (_editedProgress == null) {
_editedProgress = tp;
_originalProgress = tp;
}
}
void _onEditedProgressChanged( void _onEditedProgressChanged(
TimeProgress newProgress, bool isNewProgressValid) { TimeProgress newProgress, bool isNewProgressValid) {
setState(() { setState(() {
@ -52,87 +55,75 @@ class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
}); });
} }
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 @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;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(ProgressDetailScreen.title), title: Text(ProgressDetailScreen.title),
), ),
body: Container( body: SettingsStoreConnector(
margin: EdgeInsets.all(8), loadedBuilder: (context, settingsVm) {
child: StoreConnector( return TimeProgressStoreConnector(
onInit: loadTimeProgressListIfUnloaded, timeProgressId: args.id,
converter: (store) => timeProgressByIdSelector(store.state, args.id), loadedBuilder: (context, tpVm) {
builder: (BuildContext context, TimeProgress timeProgress) { _initEditedProgress(tpVm.tp);
if (timeProgress == null) //+++++Time Progress Not Found Error+++++ return Container(
return Center( margin: EdgeInsets.all(8),
child: Text("Error Invalid Time Progress"), child: Column(
); children: _renderColumnChildren(settingsVm, tpVm),
if (_editedProgress == null) { ));
_editedProgress = timeProgress; },
_originalProgress = timeProgress; );
} // initialize _editedProgress },
List<Widget> columnChildren = [
Expanded(
child: StoreConnector<AppState, AppSettings>(
onInit: loadSettingsIfUnloaded,
converter: (store) => appSettingsSelector(store.state),
builder: (BuildContext context, AppSettings settings) {
return ProgressViewWidget(
timeProgress: _editMode ? _editedProgress : timeProgress,
doneColor: settings.doneColor,
leftColor: settings.leftColor,
);
},
),
)
];
if (_editMode)
columnChildren.add(Expanded(
child: ProgressEditorWidget(
timeProgress: _editedProgress,
onTimeProgressChanged: _onEditedProgressChanged,
),
));
return Column(
children: columnChildren,
);
},
),
), ),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: StoreConnector( floatingActionButton: TimeProgressStoreConnector(
onInit: loadTimeProgressListIfUnloaded, timeProgressId: args.id,
converter: (store) => timeProgressByIdSelector(store.state, args.id), loadedBuilder: (context, tpVm) {
builder: (BuildContext context, TimeProgress timeProgress) { void _saveEditedProgress() {
final Store<AppState> store = StoreProvider.of<AppState>(context); tpVm.updateTimeProgress(_editedProgress);
_switchEditMode(false);
}
void _saveEditedProgress() { void _deleteTimeProgress() {
store tpVm.deleteTimeProgress();
.dispatch(UpdateTimeProgressAction(args.id, _editedProgress)); Navigator.popUntil(
_switchEditMode(false); context, ModalRoute.withName(HomeScreen.routeName));
} }
void _deleteTimeProgress() { return DetailScreenFloatingActionButtons(
store.dispatch(DeleteTimeProgressAction(args.id)); editMode: _editMode,
Navigator.popUntil( originalProgress: tpVm.tp,
context, ModalRoute.withName(HomeScreen.routeName)); editedProgress: _editedProgress,
} isEditedProgressValid: _isEditedProgressValid,
onEditProgress: () => _switchEditMode(true),
return DetailScreenFloatingActionButtons( onSaveEditedProgress: _saveEditedProgress,
editMode: _editMode, onCancelEditProgress: _cancelEditMode,
originalProgress: timeProgress, onDeleteProgress: _deleteTimeProgress);
editedProgress: _editedProgress, },
isEditedProgressValid: _isEditedProgressValid, ),
onEditProgress: () => _switchEditMode(true),
onSaveEditedProgress: _saveEditedProgress,
onCancelEditProgress: _cancelEditMode,
onDeleteProgress: _deleteTimeProgress);
}),
); );
} }
} }

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:time_progress_tracker/helper_functions.dart';
class ColorPickerButton extends StatelessWidget { class ColorPickerButton extends StatelessWidget {
final String title, dialogTitle; final String title, dialogTitle;
@ -15,13 +16,7 @@ class ColorPickerButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color getBtnPrimaryColor() => Color.fromARGB( ThemeData appTheme = Theme.of(context);
selectedColor.alpha,
selectedColor.alpha - selectedColor.red,
selectedColor.alpha - selectedColor.green,
selectedColor.alpha - selectedColor.blue,
);
return TextButton( return TextButton(
onPressed: () { onPressed: () {
showDialog( showDialog(
@ -41,7 +36,9 @@ class ColorPickerButton extends StatelessWidget {
}, },
child: Text(title), child: Text(title),
style: TextButton.styleFrom( style: TextButton.styleFrom(
primary: getBtnPrimaryColor(), primary: useBrightBackground(selectedColor)
? appTheme.primaryTextTheme.button.color
: appTheme.textTheme.button.color,
backgroundColor: selectedColor, backgroundColor: selectedColor,
), ),
); );

View File

@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/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,
);
}
}

View File

@ -11,21 +11,26 @@ class DatePickerBtn extends StatelessWidget {
@required this.onDatePicked, @required this.onDatePicked,
}) : super(); }) : super();
void _onButtonPressed(BuildContext context) async {
onDatePicked(await showDatePicker(
context: context,
initialDate: pickedDate,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
));
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
ThemeData appTheme = Theme.of(context); ThemeData appTheme = Theme.of(context);
return FlatButton( return TextButton(
onPressed: () async { onPressed: () => _onButtonPressed(context),
onDatePicked(await showDatePicker(
context: context,
initialDate: pickedDate,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
));
},
child: Text( child: Text(
"$leadingString ${pickedDate.toLocal().toString().split(" ")[0]}"), "$leadingString ${pickedDate.toLocal().toString().split(" ")[0]}"),
color: appTheme.accentColor, style: TextButton.styleFrom(
primary: appTheme.primaryTextTheme.button.color,
backgroundColor: appTheme.accentColor,
),
); );
} }
} }

View 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,
));
}
}

View File

@ -1,26 +0,0 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/widgets/progress_list_view/progress_list_tile.dart';
class HomeProgressListTile extends StatelessWidget {
final TimeProgress timeProgress;
final Color doneColor;
final Color leftColor;
HomeProgressListTile({
@required this.timeProgress,
@required this.doneColor,
@required this.leftColor,
});
@override
Widget build(BuildContext context) {
return Card(
child: ProgressListTile(
timeProgress: timeProgress,
doneColor: doneColor,
leftColor: leftColor,
),
);
}
}

View File

@ -1,51 +1,32 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:time_progress_tracker/helper_functions.dart';
import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/app_settings.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/selectors/time_progress_selectors.dart'; import 'package:time_progress_tracker/widgets/progress_list_view/progress_list_view.dart';
import 'package:time_progress_tracker/widgets/home/home_progress_list_tile.dart'; import 'package:time_progress_tracker/widgets/store_connectors/settings_store_connector.dart';
import 'package:time_progress_tracker/widgets/store_connectors/time_progress_list_store_connector.dart';
class HomeActiveProgressesTab extends StatelessWidget { class HomeActiveProgressesTab extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StoreConnector( return SettingsStoreConnector(
onInit: loadTimeProgressListIfUnloaded, loadedBuilder: (context, settingsVm) {
converter: (store) => store.state.hasProgressesLoaded, return TimeProgressListStoreConnector(
builder: (context, hasLoaded) { loadedBuilder: (context, tpListVm) {
if (!(hasLoaded as bool)) List<TimeProgress> activeTpList =
return Center( selectActiveProgresses(tpListVm.tpList);
child: CircularProgressIndicator(), if (activeTpList.length < 1)
); return Container(
return StoreConnector<AppState, AppSettings>( padding: EdgeInsets.all(16),
onInit: loadSettingsIfUnloaded, child: Center(
converter: (store) => appSettingsSelector(store.state), child: Text(
builder: (context, AppSettings settings) { "You don't have any active time progress, that are tracked."),
if (settings == null) ),
return Center(child: CircularProgressIndicator()); );
return StoreConnector<AppState, List<TimeProgress>>(
converter: (store) => activeTimeProgressesSelector(store.state), return ProgressListView(
builder: (context, List<TimeProgress> timeProgresses) { timeProgressList: activeTpList,
if (timeProgresses.length < 1) doneColor: settingsVm.appSettings.doneColor,
return Container( leftColor: settingsVm.appSettings.leftColor,
padding: EdgeInsets.all(16),
child: Center(
child: Text(
"You don't have any currently active time progresses, that are tracked."),
),
);
return ListView(
padding: EdgeInsets.all(8),
children: timeProgresses
.map((timeProgress) => HomeProgressListTile(
timeProgress: timeProgress,
doneColor: settings.doneColor,
leftColor: settings.leftColor,
))
.toList(),
);
},
); );
}, },
); );

View File

@ -1,50 +1,32 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:time_progress_tracker/helper_functions.dart';
import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/time_progress.dart'; import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/selectors/time_progress_selectors.dart'; import 'package:time_progress_tracker/widgets/progress_list_view/progress_list_view.dart';
import 'package:time_progress_tracker/widgets/home/home_progress_list_tile.dart'; import 'package:time_progress_tracker/widgets/store_connectors/settings_store_connector.dart';
import 'package:time_progress_tracker/widgets/store_connectors/time_progress_list_store_connector.dart';
class HomeInactiveProgressesTab extends StatelessWidget { class HomeInactiveProgressesTab extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StoreConnector( return SettingsStoreConnector(
onInit: loadSettingsIfUnloaded, loadedBuilder: (context, settingsVm) {
converter: (store) => appSettingsSelector(store.state), return TimeProgressListStoreConnector(
builder: (context, AppSettings settings) { loadedBuilder: (context, tpListVm) {
return StoreConnector( List<TimeProgress> inactiveTpList =
onInit: loadTimeProgressListIfUnloaded, selectInactiveProgresses(tpListVm.tpList);
converter: (store) => store.state.hasProgressesLoaded, if (inactiveTpList.length < 1)
builder: (BuildContext context, dynamic hasLoaded) { return Container(
if (!(hasLoaded as bool)) padding: EdgeInsets.all(16),
return Center( child: Center(
child: CircularProgressIndicator(), child: Text(
"You don't have any currently inactive time progresses, that are tracked."),
),
); );
return StoreConnector(
onInit: loadTimeProgressListIfUnloaded, return ProgressListView(
converter: (store) => inactiveTimeProgressesSelector(store.state), timeProgressList: inactiveTpList,
builder: doneColor: settingsVm.appSettings.doneColor,
(BuildContext context, List<TimeProgress> timeProgresses) { leftColor: settingsVm.appSettings.leftColor,
if (timeProgresses.length < 1)
return Container(
padding: EdgeInsets.all(16),
child: Center(
child: Text(
"You don't have any currently inactive time progresses, that are tracked."),
),
);
return ListView(
padding: EdgeInsets.all(8),
children: timeProgresses
.map((timeProgress) => HomeProgressListTile(
timeProgress: timeProgress,
doneColor: settings.doneColor,
leftColor: settings.leftColor,
))
.toList(),
);
},
); );
}, },
); );

View File

@ -1,21 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.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/app.dart';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_tracker/selectors/time_progress_selectors.dart';
import 'package:time_progress_tracker/widgets/home/tabs/settings/color_settings_widget.dart'; import 'package:time_progress_tracker/widgets/home/tabs/settings/color_settings_widget.dart';
import 'package:time_progress_tracker/widgets/home/tabs/settings/duration_settings_widget.dart'; import 'package:time_progress_tracker/widgets/home/tabs/settings/duration_settings_widget.dart';
import 'package:time_progress_tracker/widgets/store_connectors/settings_store_connector.dart';
class HomeSettingsTab extends StatelessWidget { class HomeSettingsTab extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StoreConnector<AppState, _ViewModel>( return SettingsStoreConnector(
onInit: loadSettingsIfUnloaded, loadedBuilder: (context, settingsVm) {
converter: (store) => _ViewModel.create(store),
builder: (context, _ViewModel vm) {
return Container( return Container(
padding: EdgeInsets.all(16), padding: EdgeInsets.all(16),
child: Center( child: Center(
@ -23,16 +16,16 @@ class HomeSettingsTab extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: ColorSettingsWidget( child: ColorSettingsWidget(
doneColor: vm.doneColor, doneColor: settingsVm.appSettings.doneColor,
leftColor: vm.leftColor, leftColor: settingsVm.appSettings.leftColor,
updateDoneColor: vm.onDoneColorChanged, updateDoneColor: settingsVm.updateDoneColor,
updateLeftColor: vm.onLeftColorChanged, updateLeftColor: settingsVm.updateLeftColor,
), ),
), ),
Expanded( Expanded(
child: DurationSettingsWidget( child: DurationSettingsWidget(
duration: vm.duration, duration: settingsVm.appSettings.duration,
updateDuration: vm.onDurationChanged, updateDuration: settingsVm.updateDuration,
), ),
), ),
Spacer(), Spacer(),
@ -57,39 +50,3 @@ class HomeSettingsTab extends StatelessWidget {
); );
} }
} }
class _ViewModel {
final Color doneColor, leftColor;
final void Function(Color) onDoneColorChanged, onLeftColorChanged;
final Duration duration;
final void Function(Duration) onDurationChanged;
_ViewModel({
@required this.doneColor,
@required this.leftColor,
@required this.onDoneColorChanged,
@required this.onLeftColorChanged,
@required this.duration,
@required this.onDurationChanged,
});
factory _ViewModel.create(Store<AppState> store) {
AppSettings settings = appSettingsSelector(store.state);
void _onDoneColorChanged(Color c) => store
.dispatch(UpdateAppSettingsActions(settings.copyWith(doneColor: c)));
void _onLeftColorChanged(Color c) => store
.dispatch(UpdateAppSettingsActions(settings.copyWith(leftColor: c)));
void _onDurationChanged(Duration d) => store
.dispatch(UpdateAppSettingsActions(settings.copyWith(duration: d)));
return _ViewModel(
doneColor: settings.doneColor,
leftColor: settings.leftColor,
onDoneColorChanged: _onDoneColorChanged,
onLeftColorChanged: _onLeftColorChanged,
duration: settings.duration,
onDurationChanged: _onDurationChanged);
}
}

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:time_progress_tracker/widgets/home/tabs/settings/color_picker_btn.dart'; import 'package:time_progress_tracker/widgets/buttons/color_picker_btn.dart';
class ColorSettingsWidget extends StatelessWidget { class ColorSettingsWidget extends StatelessWidget {
final Color doneColor, leftColor; final Color doneColor, leftColor;
@ -14,13 +14,14 @@ class ColorSettingsWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
ThemeData appTheme = Theme.of(context);
return Column( return Column(
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
"Color Settings", "Color Settings",
style: style: appTheme.textTheme.headline6,
TextStyle(fontWeight: FontWeight.bold, color: Colors.black87),
), ),
), ),
Row( Row(

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_picker/flutter_picker.dart'; import 'package:time_progress_tracker/widgets/buttons/select_duration_btn.dart';
class DurationSettingsWidget extends StatelessWidget { class DurationSettingsWidget extends StatelessWidget {
final Duration duration; final Duration duration;
@ -12,49 +12,29 @@ class DurationSettingsWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
ThemeData appTheme = Theme.of(context);
int years = duration.inDays ~/ 365; int years = duration.inDays ~/ 365;
int months = (duration.inDays - (365 * years)) ~/ 30; int months = (duration.inDays - (365 * years)) ~/ 30;
int days = duration.inDays - (365 * years) - (30 * months); int days = duration.inDays - (365 * years) - (30 * months);
return Column( return Column(
children: [ children: [
Expanded( Expanded(
child: TextButton( child: Text(
onPressed: () { "Duration Settings",
Picker( style: appTheme.textTheme.headline6,
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: 30,
suffix: Text(" D"),
),
],
),
hideHeader: true,
confirmText: "OK",
title: const Text("Select Duration"),
selectedTextStyle: TextStyle(color: Colors.blue),
onConfirm: (Picker picker, List<int> value) {
int years = value[0], months = value[1], days = value[2];
days = (years * 365) + (months * 30) + days;
Duration newDuration = Duration(days: days);
updateDuration(newDuration);
},
).showDialog(context);
},
child: Text("Default Duration: $years Y - $months M - $days D"),
), ),
), ),
Row(
children: [
Expanded(
child: SelectDurationBtn(
duration: duration,
updateDuration: updateDuration,
),
)
],
)
], ],
); );
} }

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.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/buttons/date_picker_btn.dart';
class ProgressEditorWidget extends StatefulWidget { class ProgressEditorWidget extends StatefulWidget {
final TimeProgress timeProgress; final TimeProgress timeProgress;

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:percent_indicator/linear_percent_indicator.dart'; import 'package:percent_indicator/linear_percent_indicator.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/progress_detail_screen.dart';
class ProgressListTileStrings { class ProgressListTileStrings {
static String percentString(TimeProgress tp) => static String percentString(TimeProgress tp) =>
@ -39,9 +40,14 @@ class ProgressListTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
void _onTileTap() =>
Navigator.pushNamed(context, ProgressDetailScreen.routeName,
arguments: ProgressDetailScreenArguments(timeProgress.id));
return ListTile( return ListTile(
title: Text(timeProgress.name), title: Text(timeProgress.name),
subtitle: _renderSubtitle(context), subtitle: _renderSubtitle(context),
onTap: _onTileTap,
); );
} }
} }

View File

@ -13,20 +13,21 @@ class ProgressListView extends StatelessWidget {
}); });
List<Widget> _renderListViewChildren() { List<Widget> _renderListViewChildren() {
return timeProgressList.map((e) => return timeProgressList
Card( .map((e) => Card(
child: ProgressListTile( child: ProgressListTile(
timeProgress: e, timeProgress: e,
doneColor: doneColor, doneColor: doneColor,
leftColor: leftColor, leftColor: leftColor,
), ),
) ))
).toList(growable: false); .toList(growable: false);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView( return ListView(
padding: EdgeInsets.all(8),
children: _renderListViewChildren(), children: _renderListViewChildren(),
); );
} }

View File

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/app_state.dart';
class SettingsStoreConnector extends StatelessWidget {
final Widget Function(BuildContext, SettingsViewModel) loadedBuilder;
SettingsStoreConnector({
@required this.loadedBuilder,
});
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, SettingsViewModel>(
onInit: loadSettingsIfUnloaded,
converter: (store) => SettingsViewModel._create(store),
builder: (context, SettingsViewModel vm) {
if (!vm.hasSettingsLoaded)
return Center(
child: CircularProgressIndicator(),
);
return loadedBuilder(context, vm);
},
);
}
}
class SettingsViewModel {
final AppSettings appSettings;
final bool hasSettingsLoaded;
final void Function(Color) updateDoneColor, updateLeftColor;
final void Function(Duration) updateDuration;
SettingsViewModel(
this.appSettings,
this.hasSettingsLoaded,
this.updateDoneColor,
this.updateLeftColor,
this.updateDuration,
);
factory SettingsViewModel._create(Store<AppState> store) {
AppSettings _appSettings = store.state.appSettings;
void _updateDoneColor(Color dC) => store.dispatch(
UpdateAppSettingsActions(_appSettings.copyWith(doneColor: dC)));
void _updateLeftColor(Color lC) => store.dispatch(
UpdateAppSettingsActions(_appSettings.copyWith(leftColor: lC)));
void _updateDuration(Duration d) => store
.dispatch(UpdateAppSettingsActions(_appSettings.copyWith(duration: d)));
return SettingsViewModel(_appSettings, store.state.hasSettingsLoaded,
_updateDoneColor, _updateLeftColor, _updateDuration);
}
}

View File

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
class TimeProgressListStoreConnector extends StatelessWidget {
final Widget Function(BuildContext, TimeProgressListViewModel) loadedBuilder;
TimeProgressListStoreConnector({
@required this.loadedBuilder,
});
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, TimeProgressListViewModel>(
onInit: loadTimeProgressListIfUnloaded,
converter: (store) => TimeProgressListViewModel._create(store),
builder: (context, TimeProgressListViewModel vm) {
if (!vm.hasTpListLoaded)
return Center(
child: CircularProgressIndicator(),
);
return loadedBuilder(context, vm);
},
);
}
}
class TimeProgressListViewModel {
final List<TimeProgress> tpList;
final bool hasTpListLoaded;
TimeProgressListViewModel(this.tpList, this.hasTpListLoaded);
factory TimeProgressListViewModel._create(Store<AppState> store) =>
TimeProgressListViewModel(
store.state.timeProgressList, store.state.hasProgressesLoaded);
}

View File

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import '../../helper_functions.dart';
class TimeProgressStoreConnector extends StatelessWidget {
final String timeProgressId;
final Widget Function(BuildContext, TimeProgressViewModel) loadedBuilder;
TimeProgressStoreConnector({
@required this.timeProgressId,
@required this.loadedBuilder,
});
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, TimeProgressViewModel>(
onInit: loadTimeProgressListIfUnloaded,
converter: (store) =>
TimeProgressViewModel._create(store, timeProgressId),
builder: (context, TimeProgressViewModel vm) {
if (!vm.hasTpListLoaded)
return Center(
child: CircularProgressIndicator(),
);
if (vm.tp == null)
return Center(
child: Text("Error Invalid Time Progress"),
);
return loadedBuilder(context, vm);
},
);
}
}
class TimeProgressViewModel {
final TimeProgress tp;
final bool hasTpListLoaded;
final void Function(TimeProgress) updateTimeProgress;
final void Function() deleteTimeProgress;
TimeProgressViewModel(
this.tp,
this.hasTpListLoaded,
this.updateTimeProgress,
this.deleteTimeProgress,
);
factory TimeProgressViewModel._create(Store<AppState> store, String id) {
void _updateTimeProgress(TimeProgress tp) =>
store.dispatch(UpdateTimeProgressAction(id, tp));
void _deleteTimeProgress() => store.dispatch(DeleteTimeProgressAction(id));
return TimeProgressViewModel(
selectProgressById(store.state.timeProgressList, id),
store.state.hasProgressesLoaded,
_updateTimeProgress,
_deleteTimeProgress,
);
}
}

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.18+18 version: 0.0.19+19
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: ">=2.7.0 <3.0.0"