From b520d56d1ae62ce810342fa9a0f91341168e7033 Mon Sep 17 00:00:00 2001 From: Andreas Fahrecker Date: Wed, 3 Mar 2021 16:35:08 +0100 Subject: [PATCH] Feature/change progress colors (#6) * Added Settings Actions * Created App Settings and Repo + Entity * Code cleanup Time Progress * Created App Settings Middleware * Has Progresses ad has Settings loaded * Created Load and Update Settings reducers * Added Settings store middleware to renamed store middleware * Load Default Settings if not Saved. Use Redux AppState to showprogress colors. Colors are not yet changeable. * Added ColorPicker for Done and Left Color Fixed Loading App Settings Bug * Fixed Version Number * Fixed Android App Logo * Extracted Color Settings into Widget * Fixed Home Settings Tab Layout and Color Settings Button now show Text in complementary color --- ios/Runner.xcodeproj/project.pbxproj | 18 +++-- ios/Runner/Info.plist | 2 + lib/actions/actions.dart | 24 ++++++- lib/main.dart | 11 +-- ..._middleware.dart => store_middleware.dart} | 29 ++++++-- lib/models/app_settings.dart | 44 ++++++++++++ lib/models/app_state.dart | 22 +++--- lib/models/time_progress.dart | 72 +++++++------------ lib/persistence/app_settings.dart | 49 +++++++++++++ lib/persistence/time_progress_repository.dart | 2 - lib/reducers/app_state_reducer.dart | 24 ++++++- lib/screens/progress_detail_screen.dart | 14 +++- lib/selectors/time_progress_selectors.dart | 9 +++ lib/widgets/home/home_progress_list_tile.dart | 17 +++-- .../home/tabs/home_active_progresses_tab.dart | 53 ++++++++------ .../tabs/home_inactive_progresses_tab.dart | 55 ++++++++------ lib/widgets/home/tabs/home_settings_tab.dart | 72 ++++++++++++++----- .../home/tabs/settings/color_picker_btn.dart | 49 +++++++++++++ .../tabs/settings/color_settings_widget.dart | 55 ++++++++++++++ .../tabs/settings/direct_select_item.dart | 39 ++++++++++ lib/widgets/progress_view_widget.dart | 17 +++-- pubspec.lock | 14 ++++ pubspec.yaml | 4 +- 23 files changed, 545 insertions(+), 150 deletions(-) rename lib/middleware/{store_time_progress_middleware.dart => store_middleware.dart} (59%) create mode 100644 lib/models/app_settings.dart create mode 100644 lib/persistence/app_settings.dart create mode 100644 lib/widgets/home/tabs/settings/color_picker_btn.dart create mode 100644 lib/widgets/home/tabs/settings/color_settings_widget.dart create mode 100644 lib/widgets/home/tabs/settings/direct_select_item.dart diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 82f11d0..b354908 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ @@ -132,7 +132,6 @@ FF252FCCD702699EBF6FC287 /* Pods-Runner.release.xcconfig */, F9B8D838B24E4D784CD9D717 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -354,7 +353,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -482,7 +484,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -505,7 +510,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index fbeb87b..230bb10 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Time Progress Tracker CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier diff --git a/lib/actions/actions.dart b/lib/actions/actions.dart index 8a6975d..708dcfd 100644 --- a/lib/actions/actions.dart +++ b/lib/actions/actions.dart @@ -1,7 +1,24 @@ import 'package:redux/redux.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'; +class LoadSettingsAction {} + +class AppSettingsLoadedActions { + final AppSettings appSettings; + + AppSettingsLoadedActions(this.appSettings); +} + +class UpdateAppSettingsActions { + final AppSettings appSettings; + + UpdateAppSettingsActions(this.appSettings); +} + +class AppSettingsNotLoadedAction {} + class LoadTimeProgressListAction {} class TimeProgressListLoadedAction { @@ -32,7 +49,10 @@ class DeleteTimeProgressAction { } void loadTimeProgressListIfUnloaded(Store store) { - if (!store.state.hasLoaded) { + if (!store.state.hasProgressesLoaded) store.dispatch(LoadTimeProgressListAction()); - } +} + +void loadSettingsIfUnloaded(Store store) { + if (!store.state.hasSettingsLoaded) store.dispatch(LoadSettingsAction()); } diff --git a/lib/main.dart b/lib/main.dart index 5d11f18..7c0640c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,23 +1,24 @@ import 'package:flutter/material.dart'; -import 'package:package_info/package_info.dart'; import 'package:redux/redux.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:time_progress_tracker/app.dart'; -import 'package:time_progress_tracker/middleware/store_time_progress_middleware.dart'; +import 'package:time_progress_tracker/middleware/store_middleware.dart'; import 'package:time_progress_tracker/models/app_state.dart'; +import 'package:time_progress_tracker/persistence/app_settings.dart'; import 'package:time_progress_tracker/persistence/time_progress_repository.dart'; import 'package:time_progress_tracker/reducers/app_state_reducer.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); + SharedPreferences prefs = await SharedPreferences.getInstance(); + runApp(TimeProgressTrackerApp( store: Store( appStateReducer, initialState: AppState.initial(), - middleware: createStoreTimeProgressListMiddleware( - TimeProgressRepository(await SharedPreferences.getInstance()), - ), + middleware: createStoreMiddleware( + TimeProgressRepository(prefs), AppSettingsRepository(prefs)), ), )); } diff --git a/lib/middleware/store_time_progress_middleware.dart b/lib/middleware/store_middleware.dart similarity index 59% rename from lib/middleware/store_time_progress_middleware.dart rename to lib/middleware/store_middleware.dart index 5aa9b54..b79ddaf 100644 --- a/lib/middleware/store_time_progress_middleware.dart +++ b/lib/middleware/store_middleware.dart @@ -1,21 +1,28 @@ 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/persistence/app_settings.dart'; import 'package:time_progress_tracker/persistence/time_progress_entity.dart'; import 'package:time_progress_tracker/persistence/time_progress_repository.dart'; import 'package:time_progress_tracker/selectors/time_progress_selectors.dart'; -List> createStoreTimeProgressListMiddleware( - TimeProgressRepository repository) { - final saveTimeProgressList = _createSaveTimeProgressList(repository); - final loadTimeProgressList = _createLoadTimeProgressList(repository); +List> createStoreMiddleware( + TimeProgressRepository progressRepo, AppSettingsRepository settingsRepo) { + final saveTimeProgressList = _createSaveTimeProgressList(progressRepo); + final loadTimeProgressList = _createLoadTimeProgressList(progressRepo); + + final saveSettings = _createSaveAppSettings(settingsRepo); + final loadSettings = _createLoadAppSettings(settingsRepo); return [ TypedMiddleware(loadTimeProgressList), TypedMiddleware(saveTimeProgressList), TypedMiddleware(saveTimeProgressList), TypedMiddleware(saveTimeProgressList), + TypedMiddleware(loadSettings), + TypedMiddleware(saveSettings) ]; } @@ -47,3 +54,17 @@ Middleware _createLoadTimeProgressList( }).catchError((_) => store.dispatch(TimeProgressListNotLoadedAction())); }; } + +Middleware _createSaveAppSettings(AppSettingsRepository repo) => + (Store store, dynamic action, NextDispatcher next) { + next(action); + repo.saveAppSettings(store.state.appSettings.toEntity()); + }; + +Middleware _createLoadAppSettings(AppSettingsRepository repo) => + (Store store, dynamic action, NextDispatcher next) { + repo.loadAppSettings().then((appSettings) { + store.dispatch( + AppSettingsLoadedActions(AppSettings.fromEntity(appSettings))); + }); + }; diff --git a/lib/models/app_settings.dart b/lib/models/app_settings.dart new file mode 100644 index 0000000..919c6f5 --- /dev/null +++ b/lib/models/app_settings.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:time_progress_tracker/persistence/app_settings.dart'; + +@immutable +class AppSettings { + final Color doneColor; + final Color leftColor; + final Duration duration; + + AppSettings({ + this.doneColor, + this.leftColor, + this.duration, + }); + + factory AppSettings.defaults() => + AppSettings(doneColor: Colors.green, leftColor: Colors.red); + + AppSettings copyWith({ + Color doneColor, + Color leftColor, + }) => + AppSettings( + doneColor: doneColor ?? this.doneColor, + leftColor: leftColor ?? this.leftColor); + + @override + int get hashCode => doneColor.hashCode ^ leftColor.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AppSettings && + runtimeType == other.runtimeType && + doneColor == other.doneColor && + leftColor == other.leftColor; + + AppSettingsEntity toEntity() => + AppSettingsEntity(doneColor.value, leftColor.value); + + static AppSettings fromEntity(AppSettingsEntity entity) => AppSettings( + doneColor: Color(entity.doneColorValue), + leftColor: Color(entity.leftColorValue)); +} diff --git a/lib/models/app_state.dart b/lib/models/app_state.dart index b436b7e..350aa42 100644 --- a/lib/models/app_state.dart +++ b/lib/models/app_state.dart @@ -1,36 +1,40 @@ import 'package:meta/meta.dart'; +import 'package:time_progress_tracker/models/app_settings.dart'; import 'package:time_progress_tracker/models/time_progress.dart'; @immutable class AppState { - final bool hasLoaded; + final bool hasProgressesLoaded, hasSettingsLoaded; final List timeProgressList; + final AppSettings appSettings; - AppState({ - this.hasLoaded = false, - this.timeProgressList = const [], - }); + AppState( + {this.hasProgressesLoaded = false, + this.hasSettingsLoaded = false, + this.timeProgressList = const [], + this.appSettings}); - factory AppState.initial() => AppState(hasLoaded: false); + factory AppState.initial() => + AppState(hasProgressesLoaded: false, appSettings: AppSettings.defaults()); AppState copyWith({ bool hasLoaded, List timeProgressList, }) { return AppState( - hasLoaded: hasLoaded ?? this.hasLoaded, + hasProgressesLoaded: hasLoaded ?? this.hasProgressesLoaded, timeProgressList: timeProgressList ?? this.timeProgressList, ); } @override - int get hashCode => hasLoaded.hashCode ^ timeProgressList.hashCode; + int get hashCode => hasProgressesLoaded.hashCode ^ timeProgressList.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is AppState && runtimeType == other.runtimeType && - hasLoaded == other.hasLoaded && + hasProgressesLoaded == other.hasProgressesLoaded && timeProgressList == other.timeProgressList; } diff --git a/lib/models/time_progress.dart b/lib/models/time_progress.dart index bdf67fa..7b8d89d 100644 --- a/lib/models/time_progress.dart +++ b/lib/models/time_progress.dart @@ -20,26 +20,19 @@ class TimeProgress { } TimeProgress copyWith( - {String id, String name, DateTime startTime, DateTime endTime}) { - return TimeProgress( - name ?? this.name, - startTime ?? this.startTime, - endTime ?? this.endTime, - id: id ?? this.id, - ); - } + {String id, String name, DateTime startTime, DateTime endTime}) => + TimeProgress( + name ?? this.name, + startTime ?? this.startTime, + endTime ?? this.endTime, + id: id ?? this.id, + ); - int daysBehind() { - return DateTime.now().difference(startTime).inDays; - } + int daysBehind() => DateTime.now().difference(startTime).inDays; - int daysLeft() { - return endTime.difference(DateTime.now()).inDays; - } + int daysLeft() => endTime.difference(DateTime.now()).inDays; - int allDays() { - return endTime.difference(startTime).inDays; - } + int allDays() => endTime.difference(startTime).inDays; double percentDone() { double percent = this.daysBehind() / (this.allDays() / 100) / 100; @@ -48,15 +41,11 @@ class TimeProgress { return percent; } - bool hasStarted() { - return DateTime.now().millisecondsSinceEpoch > - startTime.millisecondsSinceEpoch; - } + bool hasStarted() => + DateTime.now().millisecondsSinceEpoch > startTime.millisecondsSinceEpoch; - bool hasEnded() { - return DateTime.now().millisecondsSinceEpoch > - endTime.millisecondsSinceEpoch; - } + bool hasEnded() => + DateTime.now().millisecondsSinceEpoch > endTime.millisecondsSinceEpoch; @override int get hashCode => @@ -73,9 +62,8 @@ class TimeProgress { endTime == other.endTime; @override - String toString() { - return "TimeProgress{id: $id, name: $name, startTime: $startTime, endTime: $endTime}"; - } + String toString() => + "TimeProgress{id: $id, name: $name, startTime: $startTime, endTime: $endTime}"; TimeProgressEntity toEntity() { if (!TimeProgress.isNameValid(name)) @@ -86,25 +74,17 @@ class TimeProgress { return TimeProgressEntity(id, name, startTime, endTime); } - static TimeProgress fromEntity(TimeProgressEntity entity) { - return TimeProgress( - entity.name, - entity.startTime, - entity.endTime, - id: entity.id ?? Uuid().generateV4(), - ); - } + static TimeProgress fromEntity(TimeProgressEntity entity) => + TimeProgress(entity.name, entity.startTime, entity.endTime, + id: entity.id ?? Uuid().generateV4()); - static bool isValid(TimeProgress tp) { - return TimeProgress.isNameValid(tp.name) && - TimeProgress.areTimesValid(tp.startTime, tp.endTime); - } + static bool isValid(TimeProgress tp) => + TimeProgress.isNameValid(tp.name) && + TimeProgress.areTimesValid(tp.startTime, tp.endTime); - static bool isNameValid(String name) { - return name != null && name != "" && name.length > 2 && name.length < 21; - } + static bool isNameValid(String name) => + name != null && name != "" && name.length > 2 && name.length < 21; - static bool areTimesValid(DateTime startTime, DateTime endTime) { - return startTime.isBefore(endTime); - } + static bool areTimesValid(DateTime startTime, DateTime endTime) => + startTime.isBefore(endTime); } diff --git a/lib/persistence/app_settings.dart b/lib/persistence/app_settings.dart new file mode 100644 index 0000000..b071400 --- /dev/null +++ b/lib/persistence/app_settings.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; + +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:time_progress_tracker/models/app_settings.dart'; + +class AppSettingsRepository { + static const String _key = "app_settings"; + final SharedPreferences prefs; + final JsonCodec codec; + + AppSettingsRepository(this.prefs, {this.codec = json}); + + Future loadAppSettings() { + final String jsonString = this.prefs.getString(_key); + if (jsonString == null) + return Future.value(AppSettingsEntity.defaults()); + return Future.value( + AppSettingsEntity.fromJson(codec.decode(jsonString))); + } + + Future saveAppSettings(AppSettingsEntity appSettings) => + this.prefs.setString(_key, codec.encode(appSettings)); +} + +class AppSettingsEntity { + static const String _doneKey = "doneColorValue", _leftKey = "leftColorValue"; + final int doneColorValue, leftColorValue; + + AppSettingsEntity(this.doneColorValue, this.leftColorValue); + + factory AppSettingsEntity.defaults() => AppSettings.defaults().toEntity(); + + @override + int get hashCode => doneColorValue.hashCode ^ leftColorValue.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AppSettingsEntity && + runtimeType == other.runtimeType && + doneColorValue == other.doneColorValue && + leftColorValue == other.leftColorValue; + + Map toJson() => + {_doneKey: doneColorValue, _leftKey: leftColorValue}; + + static AppSettingsEntity fromJson(Map json) => + AppSettingsEntity(json[_doneKey], json[_leftKey]); +} diff --git a/lib/persistence/time_progress_repository.dart b/lib/persistence/time_progress_repository.dart index 1fb8765..0b87123 100644 --- a/lib/persistence/time_progress_repository.dart +++ b/lib/persistence/time_progress_repository.dart @@ -2,8 +2,6 @@ import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:time_progress_tracker/persistence/time_progress_entity.dart'; -import 'dart:developer' as developer; - class TimeProgressRepository { static const String _key = "time_progress_repo"; final SharedPreferences prefs; diff --git a/lib/reducers/app_state_reducer.dart b/lib/reducers/app_state_reducer.dart index b838eb6..d8787cd 100644 --- a/lib/reducers/app_state_reducer.dart +++ b/lib/reducers/app_state_reducer.dart @@ -1,10 +1,32 @@ +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/reducers/has_loaded_reducer.dart'; import 'package:time_progress_tracker/reducers/time_progress_list_reducer.dart'; AppState appStateReducer(AppState state, dynamic action) { return AppState( - hasLoaded: hasLoadedReducer(state.hasLoaded, action), + hasProgressesLoaded: hasLoadedReducer(state.hasProgressesLoaded, action), timeProgressList: timeProgressListReducer(state.timeProgressList, action), + appSettings: appSettingsReducers(state.appSettings, action), ); } + +final appSettingsReducers = combineReducers([ + TypedReducer(_loadAppSettings), + TypedReducer(_updateAppSettings), + TypedReducer(_setDefaultSettings) +]); + +AppSettings _loadAppSettings( + AppSettings appSettings, AppSettingsLoadedActions nS) => + nS.appSettings; + +AppSettings _setDefaultSettings( + AppSettings appSettings, AppSettingsNotLoadedAction action) => + AppSettings.defaults(); + +AppSettings _updateAppSettings( + AppSettings appSettings, UpdateAppSettingsActions nS) => + nS.appSettings; diff --git a/lib/screens/progress_detail_screen.dart b/lib/screens/progress_detail_screen.dart index ec753d8..dbb3bab 100644 --- a/lib/screens/progress_detail_screen.dart +++ b/lib/screens/progress_detail_screen.dart @@ -2,6 +2,7 @@ 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/screens/home_screen.dart'; @@ -76,8 +77,17 @@ class _ProgressDetailScreenState extends State { List columnChildren = [ Expanded( - child: ProgressViewWidget( - timeProgress: _editMode ? _editedProgress : timeProgress), + child: StoreConnector( + 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) diff --git a/lib/selectors/time_progress_selectors.dart b/lib/selectors/time_progress_selectors.dart index ecf939b..cf77f79 100644 --- a/lib/selectors/time_progress_selectors.dart +++ b/lib/selectors/time_progress_selectors.dart @@ -1,3 +1,6 @@ +import 'dart:ui'; + +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'; @@ -47,3 +50,9 @@ TimeProgress timeProgressByIdSelector(AppState state, String id) { return state.timeProgressList .firstWhere((timeProgress) => timeProgress.id == id, orElse: () => null); } + +AppSettings appSettingsSelector(AppState state) { + return state.appSettings; +} + +Color doneColorSelector(AppState state) => state.appSettings.doneColor; diff --git a/lib/widgets/home/home_progress_list_tile.dart b/lib/widgets/home/home_progress_list_tile.dart index 996531a..b62f3b5 100644 --- a/lib/widgets/home/home_progress_list_tile.dart +++ b/lib/widgets/home/home_progress_list_tile.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:percent_indicator/linear_percent_indicator.dart'; import 'package:time_progress_tracker/models/time_progress.dart'; @@ -6,21 +5,27 @@ import 'package:time_progress_tracker/screens/progress_detail_screen.dart'; class HomeProgressListTile extends StatelessWidget { final TimeProgress timeProgress; + final Color doneColor; + final Color leftColor; HomeProgressListTile({ - Key key, @required this.timeProgress, - }) : super(key: key); + @required this.doneColor, + @required this.leftColor, + }); @override Widget build(BuildContext context) { Widget listTileSubTitle; if (timeProgress.hasStarted() && !timeProgress.hasEnded()) listTileSubTitle = LinearPercentIndicator( - center: Text("${(timeProgress.percentDone() * 100).floor()} %"), + center: Text( + "${(timeProgress.percentDone() * 100).floor()} %", + style: TextStyle(color: Colors.white), + ), percent: timeProgress.percentDone(), - progressColor: Colors.green, - backgroundColor: Colors.red, + progressColor: doneColor, + backgroundColor: leftColor, lineHeight: 20, ); if (!timeProgress.hasStarted()) diff --git a/lib/widgets/home/tabs/home_active_progresses_tab.dart b/lib/widgets/home/tabs/home_active_progresses_tab.dart index 6b5e700..57af28d 100644 --- a/lib/widgets/home/tabs/home_active_progresses_tab.dart +++ b/lib/widgets/home/tabs/home_active_progresses_tab.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_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/home/home_progress_list_tile.dart'; @@ -10,31 +12,40 @@ class HomeActiveProgressesTab extends StatelessWidget { Widget build(BuildContext context) { return StoreConnector( onInit: loadTimeProgressListIfUnloaded, - converter: (store) => store.state.hasLoaded, - builder: (BuildContext context, dynamic hasLoaded) { + converter: (store) => store.state.hasProgressesLoaded, + builder: (context, hasLoaded) { if (!(hasLoaded as bool)) return Center( child: CircularProgressIndicator(), ); - return StoreConnector( - onInit: loadTimeProgressListIfUnloaded, - converter: (store) => activeTimeProgressesSelector(store.state), - builder: (BuildContext context, List timeProgresses) { - if (timeProgresses.length < 1) - return Container( - 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, - )) - .toList(), + return StoreConnector( + onInit: loadSettingsIfUnloaded, + converter: (store) => appSettingsSelector(store.state), + builder: (context, AppSettings settings) { + if (settings == null) + return Center(child: CircularProgressIndicator()); + return StoreConnector>( + converter: (store) => activeTimeProgressesSelector(store.state), + builder: (context, List timeProgresses) { + if (timeProgresses.length < 1) + return Container( + 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(), + ); + }, ); }, ); diff --git a/lib/widgets/home/tabs/home_inactive_progresses_tab.dart b/lib/widgets/home/tabs/home_inactive_progresses_tab.dart index c1e7507..8fa2e60 100644 --- a/lib/widgets/home/tabs/home_inactive_progresses_tab.dart +++ b/lib/widgets/home/tabs/home_inactive_progresses_tab.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_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/time_progress.dart'; import 'package:time_progress_tracker/selectors/time_progress_selectors.dart'; import 'package:time_progress_tracker/widgets/home/home_progress_list_tile.dart'; @@ -9,31 +10,41 @@ class HomeInactiveProgressesTab extends StatelessWidget { @override Widget build(BuildContext context) { return StoreConnector( - onInit: loadTimeProgressListIfUnloaded, - converter: (store) => store.state.hasLoaded, - builder: (BuildContext context, dynamic hasLoaded) { - if (!(hasLoaded as bool)) - return Center( - child: CircularProgressIndicator(), - ); + onInit: loadSettingsIfUnloaded, + converter: (store) => appSettingsSelector(store.state), + builder: (context, AppSettings settings) { return StoreConnector( onInit: loadTimeProgressListIfUnloaded, - converter: (store) => inactiveTimeProgressesSelector(store.state), - builder: (BuildContext context, List timeProgresses) { - 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."), - ), + converter: (store) => store.state.hasProgressesLoaded, + builder: (BuildContext context, dynamic hasLoaded) { + if (!(hasLoaded as bool)) + return Center( + child: CircularProgressIndicator(), ); - return ListView( - padding: EdgeInsets.all(8), - children: timeProgresses - .map((timeProgress) => - HomeProgressListTile(timeProgress: timeProgress)) - .toList(), + return StoreConnector( + onInit: loadTimeProgressListIfUnloaded, + converter: (store) => inactiveTimeProgressesSelector(store.state), + builder: + (BuildContext context, List timeProgresses) { + 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(), + ); + }, ); }, ); diff --git a/lib/widgets/home/tabs/home_settings_tab.dart b/lib/widgets/home/tabs/home_settings_tab.dart index 5d3f62b..4af6a3c 100644 --- a/lib/widgets/home/tabs/home_settings_tab.dart +++ b/lib/widgets/home/tabs/home_settings_tab.dart @@ -1,28 +1,62 @@ import 'package:flutter/material.dart'; -import 'package:package_info/package_info.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/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'; class HomeSettingsTab extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - padding: EdgeInsets.all(16), - child: Center( - child: Column( - children: [ - Text("The Settings of this App are not yet implemented."), - FlatButton( - onPressed: () { - showAboutDialog( - context: context, - applicationName: TimeProgressTrackerApp.name, - applicationVersion: "Beta", - applicationLegalese: '\u00a9Andreas Fahrecker 2020-2021'); - }, - child: Text("About")) - ], - ), - ), + return StoreConnector( + onInit: loadSettingsIfUnloaded, + converter: (store) => appSettingsSelector(store.state), + builder: (context, AppSettings settings) { + Store store = StoreProvider.of(context); + void updateDoneColor(Color newDoneColor) => store.dispatch( + UpdateAppSettingsActions( + settings.copyWith(doneColor: newDoneColor)), + ); + void updateLeftColor(Color newLeftColor) => store.dispatch( + UpdateAppSettingsActions( + settings.copyWith(leftColor: newLeftColor)), + ); + + return Container( + padding: EdgeInsets.all(16), + child: Center( + child: Column( + children: [ + Expanded( + child: ColorSettingsWidget( + doneColor: settings.doneColor, + leftColor: settings.leftColor, + updateDoneColor: updateDoneColor, + updateLeftColor: updateLeftColor, + ), + ), + Spacer(), + Expanded( + child: TextButton( + onPressed: () { + showAboutDialog( + context: context, + applicationName: TimeProgressTrackerApp.name, + applicationVersion: "Beta", + applicationLegalese: + '\u00a9Andreas Fahrecker 2020-2021'); + }, + child: Text("About"), + ), + ), + ], + ), + ), + ); + }, ); } } diff --git a/lib/widgets/home/tabs/settings/color_picker_btn.dart b/lib/widgets/home/tabs/settings/color_picker_btn.dart new file mode 100644 index 0000000..b4be9d3 --- /dev/null +++ b/lib/widgets/home/tabs/settings/color_picker_btn.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_colorpicker/flutter_colorpicker.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) { + Color getBtnPrimaryColor() => Color.fromARGB( + selectedColor.alpha, + selectedColor.alpha - selectedColor.red, + selectedColor.alpha - selectedColor.green, + selectedColor.alpha - selectedColor.blue, + ); + + 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: getBtnPrimaryColor(), + backgroundColor: selectedColor, + ), + ); + } +} diff --git a/lib/widgets/home/tabs/settings/color_settings_widget.dart b/lib/widgets/home/tabs/settings/color_settings_widget.dart new file mode 100644 index 0000000..4282517 --- /dev/null +++ b/lib/widgets/home/tabs/settings/color_settings_widget.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:time_progress_tracker/widgets/home/tabs/settings/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) { + return Column( + children: [ + Expanded( + child: Text( + "Color Settings", + style: + TextStyle(fontWeight: FontWeight.bold, color: Colors.black87), + ), + ), + 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, + ), + ), + ), + ], + ) + ], + ); + } +} diff --git a/lib/widgets/home/tabs/settings/direct_select_item.dart b/lib/widgets/home/tabs/settings/direct_select_item.dart new file mode 100644 index 0000000..fad22ab --- /dev/null +++ b/lib/widgets/home/tabs/settings/direct_select_item.dart @@ -0,0 +1,39 @@ +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), + ); + } +} diff --git a/lib/widgets/progress_view_widget.dart b/lib/widgets/progress_view_widget.dart index 58e2dde..ca5b205 100644 --- a/lib/widgets/progress_view_widget.dart +++ b/lib/widgets/progress_view_widget.dart @@ -5,9 +5,13 @@ 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 @@ -33,8 +37,8 @@ class ProgressViewWidget extends StatelessWidget { radius: 100, lineWidth: 10, percent: timeProgress.percentDone(), - progressColor: Colors.green, - backgroundColor: Colors.red, + progressColor: doneColor, + backgroundColor: leftColor, center: Text("${(timeProgress.percentDone() * 100).floor()} %"), ), ), @@ -43,10 +47,13 @@ class ProgressViewWidget extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 15), percent: timeProgress.percentDone(), leading: Text("${timeProgress.daysBehind()} Days"), - center: Text("${(timeProgress.percentDone() * 100).floor()} %"), + center: Text( + "${(timeProgress.percentDone() * 100).floor()} %", + style: TextStyle(color: Colors.white), + ), trailing: Text("${timeProgress.daysLeft()} Days"), - progressColor: Colors.green, - backgroundColor: Colors.red, + progressColor: doneColor, + backgroundColor: leftColor, lineHeight: 25, ), ), diff --git a/pubspec.lock b/pubspec.lock index 37c1d4c..9ffd8b6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -78,6 +78,13 @@ 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: @@ -104,6 +111,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_colorpicker: + dependency: "direct main" + description: + name: flutter_colorpicker + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.5" flutter_launcher_icons: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index adb6502..6ceb5b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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.14+14 +version: 0.0.17+17 environment: sdk: ">=2.7.0 <3.0.0" @@ -23,7 +23,9 @@ environment: dependencies: flutter: sdk: flutter + flutter_colorpicker: flutter_redux: + direct_select: meta: package_info: percent_indicator: