Feature/default duration setting (#7)

* Implemented Basic Duration Settings into AppSettings Model

* Implemented Basic Duration Settings into AppSettings Model

* Created Duration Settings Widget and Started using ViewModel in HomeSettingsTab

* Updated Version Number
This commit is contained in:
Andreas Fahrecker 2021-03-03 19:59:33 +01:00 committed by GitHub
parent b520d56d1a
commit 90f2998088
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 219 additions and 96 deletions

View File

@ -13,19 +13,26 @@ class AppSettings {
this.duration,
});
factory AppSettings.defaults() =>
AppSettings(doneColor: Colors.green, leftColor: Colors.red);
factory AppSettings.defaults() => AppSettings(
doneColor: Colors.green,
leftColor: Colors.red,
duration: Duration(days: 365),
);
AppSettings copyWith({
Color doneColor,
Color leftColor,
Duration duration,
}) =>
AppSettings(
doneColor: doneColor ?? this.doneColor,
leftColor: leftColor ?? this.leftColor);
doneColor: doneColor ?? this.doneColor,
leftColor: leftColor ?? this.leftColor,
duration: duration ?? this.duration,
);
@override
int get hashCode => doneColor.hashCode ^ leftColor.hashCode;
int get hashCode =>
doneColor.hashCode ^ leftColor.hashCode ^ duration.hashCode;
@override
bool operator ==(Object other) =>
@ -33,12 +40,15 @@ class AppSettings {
other is AppSettings &&
runtimeType == other.runtimeType &&
doneColor == other.doneColor &&
leftColor == other.leftColor;
leftColor == other.leftColor &&
duration == other.duration;
AppSettingsEntity toEntity() =>
AppSettingsEntity(doneColor.value, leftColor.value);
AppSettingsEntity(doneColor.value, leftColor.value, duration.inDays);
static AppSettings fromEntity(AppSettingsEntity entity) => AppSettings(
doneColor: Color(entity.doneColorValue),
leftColor: Color(entity.leftColorValue));
doneColor: Color(entity.doneColorValue),
leftColor: Color(entity.leftColorValue),
duration: Duration(days: entity.durationDays),
);
}

View File

@ -19,6 +19,9 @@ class TimeProgress {
"Initial Name", DateTime(thisYear - 1), DateTime(thisYear + 1));
}
factory TimeProgress.defaultFromDuration(Duration duration) =>
TimeProgress("", DateTime.now(), DateTime.now().add(duration));
TimeProgress copyWith(
{String id, String name, DateTime startTime, DateTime endTime}) =>
TimeProgress(

View File

@ -23,10 +23,13 @@ class AppSettingsRepository {
}
class AppSettingsEntity {
static const String _doneKey = "doneColorValue", _leftKey = "leftColorValue";
final int doneColorValue, leftColorValue;
static const String _doneKey = "doneColorValue",
_leftKey = "leftColorValue",
_durationDaysKey = "durationDays";
final int doneColorValue, leftColorValue, durationDays;
AppSettingsEntity(this.doneColorValue, this.leftColorValue);
AppSettingsEntity(
this.doneColorValue, this.leftColorValue, this.durationDays);
factory AppSettingsEntity.defaults() => AppSettings.defaults().toEntity();
@ -41,9 +44,16 @@ class AppSettingsEntity {
doneColorValue == other.doneColorValue &&
leftColorValue == other.leftColorValue;
Map<String, Object> toJson() =>
{_doneKey: doneColorValue, _leftKey: leftColorValue};
Map<String, Object> toJson() => {
_doneKey: doneColorValue,
_leftKey: leftColorValue,
_durationDaysKey: durationDays,
};
static AppSettingsEntity fromJson(Map<String, Object> json) =>
AppSettingsEntity(json[_doneKey], json[_leftKey]);
AppSettingsEntity(
json[_doneKey],
json[_leftKey],
json[_durationDaysKey],
);
}

View File

@ -1,9 +1,12 @@
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/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/selectors/time_progress_selectors.dart';
import 'package:time_progress_tracker/widgets/progress_editor_widget.dart';
class ProgressCreationScreen extends StatefulWidget {
@ -17,10 +20,16 @@ class ProgressCreationScreen extends StatefulWidget {
}
class _ProgressCreationScreenState extends State<ProgressCreationScreen> {
TimeProgress timeProgressToCreate =
TimeProgress("", DateTime.now(), DateTime(DateTime.now().year + 1));
TimeProgress timeProgressToCreate;
bool _isProgressValid = false;
void initTimeProgress(TimeProgress timeProgress) {
if (timeProgressToCreate == null)
setState(() {
timeProgressToCreate = timeProgress;
});
}
void onTimeProgressChanged(
TimeProgress newTimeProgress, bool isNewProgressValid) {
setState(() {
@ -37,25 +46,34 @@ class _ProgressCreationScreenState extends State<ProgressCreationScreen> {
),
body: Container(
padding: EdgeInsets.all(12),
child: ProgressEditorWidget(
timeProgress: timeProgressToCreate,
onTimeProgressChanged: onTimeProgressChanged,
),
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: FloatingActionButton(
heroTag: "createTimeProgressBTN",
child: Icon(Icons.save),
onPressed: _isProgressValid
? () {
StoreProvider.of<AppState>(context).dispatch(
AddTimeProgressAction(timeProgressToCreate));
Navigator.pop(context);
}
: null,
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(
@ -72,3 +90,27 @@ class _ProgressCreationScreenState extends State<ProgressCreationScreen> {
);
}
}
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,
);
}
}

View File

@ -7,24 +7,15 @@ 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/duration_settings_widget.dart';
class HomeSettingsTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, AppSettings>(
return StoreConnector<AppState, _ViewModel>(
onInit: loadSettingsIfUnloaded,
converter: (store) => appSettingsSelector(store.state),
builder: (context, AppSettings settings) {
Store<AppState> store = StoreProvider.of<AppState>(context);
void updateDoneColor(Color newDoneColor) => store.dispatch(
UpdateAppSettingsActions(
settings.copyWith(doneColor: newDoneColor)),
);
void updateLeftColor(Color newLeftColor) => store.dispatch(
UpdateAppSettingsActions(
settings.copyWith(leftColor: newLeftColor)),
);
converter: (store) => _ViewModel.create(store),
builder: (context, _ViewModel vm) {
return Container(
padding: EdgeInsets.all(16),
child: Center(
@ -32,10 +23,16 @@ class HomeSettingsTab extends StatelessWidget {
children: [
Expanded(
child: ColorSettingsWidget(
doneColor: settings.doneColor,
leftColor: settings.leftColor,
updateDoneColor: updateDoneColor,
updateLeftColor: updateLeftColor,
doneColor: vm.doneColor,
leftColor: vm.leftColor,
updateDoneColor: vm.onDoneColorChanged,
updateLeftColor: vm.onLeftColorChanged,
),
),
Expanded(
child: DurationSettingsWidget(
duration: vm.duration,
updateDuration: vm.onDurationChanged,
),
),
Spacer(),
@ -60,3 +57,39 @@ 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,39 +0,0 @@
import 'package:flutter/material.dart';
class DirectSelectItem extends StatelessWidget {
final String title;
final bool isForList;
DirectSelectItem({this.title, this.isForList});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 60,
child: isForList
? Padding(
child: _buildItem(context),
padding: EdgeInsets.all(10),
)
: Card(
margin: EdgeInsets.symmetric(horizontal: 10),
child: Stack(
children: [
_buildItem(context),
Align(
alignment: Alignment.centerRight,
child: Icon(Icons.arrow_drop_down),
)
],
),
));
}
Container _buildItem(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
alignment: Alignment.center,
child: Text(title),
);
}
}

View File

@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:flutter_picker/flutter_picker.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) {
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: TextButton(
onPressed: () {
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: 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"),
),
),
],
);
}
}

View File

@ -78,13 +78,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
direct_select:
dependency: "direct main"
description:
name: direct_select
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
fake_async:
dependency: transitive
description:
@ -125,6 +118,15 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.1"
flutter_picker:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: e95d121f54faba889fbf8a850c86dd5cf4aa5c5a
url: "git://github.com/yangyxd/flutter_picker.git"
source: git
version: "1.1.5"
flutter_redux:
dependency: "direct main"
description:

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.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.0.17+17
version: 0.0.18+18
environment:
sdk: ">=2.7.0 <3.0.0"
@ -25,7 +25,8 @@ dependencies:
sdk: flutter
flutter_colorpicker:
flutter_redux:
direct_select:
flutter_picker:
git: git://github.com/yangyxd/flutter_picker.git
meta:
package_info:
percent_indicator: