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
This commit is contained in:
Andreas Fahrecker 2021-03-03 16:35:08 +01:00 committed by GitHub
parent c580e45361
commit b520d56d1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 545 additions and 150 deletions

View File

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 46; objectVersion = 50;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -132,7 +132,6 @@
FF252FCCD702699EBF6FC287 /* Pods-Runner.release.xcconfig */, FF252FCCD702699EBF6FC287 /* Pods-Runner.release.xcconfig */,
F9B8D838B24E4D784CD9D717 /* Pods-Runner.profile.xcconfig */, F9B8D838B24E4D784CD9D717 /* Pods-Runner.profile.xcconfig */,
); );
name = Pods;
path = Pods; path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -354,7 +353,10 @@
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
@ -482,7 +484,10 @@
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
@ -505,7 +510,10 @@
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
); );
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",

View File

@ -4,6 +4,8 @@
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Time Progress Tracker</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>

View File

@ -1,7 +1,24 @@
import 'package:redux/redux.dart'; 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/app_state.dart';
import 'package:time_progress_tracker/models/time_progress.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 LoadTimeProgressListAction {}
class TimeProgressListLoadedAction { class TimeProgressListLoadedAction {
@ -32,7 +49,10 @@ class DeleteTimeProgressAction {
} }
void loadTimeProgressListIfUnloaded(Store<AppState> store) { void loadTimeProgressListIfUnloaded(Store<AppState> store) {
if (!store.state.hasLoaded) { if (!store.state.hasProgressesLoaded)
store.dispatch(LoadTimeProgressListAction()); store.dispatch(LoadTimeProgressListAction());
} }
void loadSettingsIfUnloaded(Store<AppState> store) {
if (!store.state.hasSettingsLoaded) store.dispatch(LoadSettingsAction());
} }

View File

@ -1,23 +1,24 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:package_info/package_info.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:time_progress_tracker/app.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/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/persistence/time_progress_repository.dart';
import 'package:time_progress_tracker/reducers/app_state_reducer.dart'; import 'package:time_progress_tracker/reducers/app_state_reducer.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
runApp(TimeProgressTrackerApp( runApp(TimeProgressTrackerApp(
store: Store<AppState>( store: Store<AppState>(
appStateReducer, appStateReducer,
initialState: AppState.initial(), initialState: AppState.initial(),
middleware: createStoreTimeProgressListMiddleware( middleware: createStoreMiddleware(
TimeProgressRepository(await SharedPreferences.getInstance()), TimeProgressRepository(prefs), AppSettingsRepository(prefs)),
),
), ),
)); ));
} }

View File

@ -1,21 +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';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/app_state.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/persistence/app_settings.dart';
import 'package:time_progress_tracker/persistence/time_progress_entity.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/persistence/time_progress_repository.dart';
import 'package:time_progress_tracker/selectors/time_progress_selectors.dart'; import 'package:time_progress_tracker/selectors/time_progress_selectors.dart';
List<Middleware<AppState>> createStoreTimeProgressListMiddleware( List<Middleware<AppState>> createStoreMiddleware(
TimeProgressRepository repository) { TimeProgressRepository progressRepo, AppSettingsRepository settingsRepo) {
final saveTimeProgressList = _createSaveTimeProgressList(repository); final saveTimeProgressList = _createSaveTimeProgressList(progressRepo);
final loadTimeProgressList = _createLoadTimeProgressList(repository); final loadTimeProgressList = _createLoadTimeProgressList(progressRepo);
final saveSettings = _createSaveAppSettings(settingsRepo);
final loadSettings = _createLoadAppSettings(settingsRepo);
return [ return [
TypedMiddleware<AppState, LoadTimeProgressListAction>(loadTimeProgressList), TypedMiddleware<AppState, LoadTimeProgressListAction>(loadTimeProgressList),
TypedMiddleware<AppState, AddTimeProgressAction>(saveTimeProgressList), TypedMiddleware<AppState, AddTimeProgressAction>(saveTimeProgressList),
TypedMiddleware<AppState, UpdateTimeProgressAction>(saveTimeProgressList), TypedMiddleware<AppState, UpdateTimeProgressAction>(saveTimeProgressList),
TypedMiddleware<AppState, DeleteTimeProgressAction>(saveTimeProgressList), TypedMiddleware<AppState, DeleteTimeProgressAction>(saveTimeProgressList),
TypedMiddleware<AppState, LoadSettingsAction>(loadSettings),
TypedMiddleware<AppState, UpdateAppSettingsActions>(saveSettings)
]; ];
} }
@ -47,3 +54,17 @@ Middleware<AppState> _createLoadTimeProgressList(
}).catchError((_) => store.dispatch(TimeProgressListNotLoadedAction())); }).catchError((_) => store.dispatch(TimeProgressListNotLoadedAction()));
}; };
} }
Middleware<AppState> _createSaveAppSettings(AppSettingsRepository repo) =>
(Store<AppState> store, dynamic action, NextDispatcher next) {
next(action);
repo.saveAppSettings(store.state.appSettings.toEntity());
};
Middleware<AppState> _createLoadAppSettings(AppSettingsRepository repo) =>
(Store<AppState> store, dynamic action, NextDispatcher next) {
repo.loadAppSettings().then((appSettings) {
store.dispatch(
AppSettingsLoadedActions(AppSettings.fromEntity(appSettings)));
});
};

View File

@ -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));
}

View File

@ -1,36 +1,40 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.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';
@immutable @immutable
class AppState { class AppState {
final bool hasLoaded; final bool hasProgressesLoaded, hasSettingsLoaded;
final List<TimeProgress> timeProgressList; final List<TimeProgress> timeProgressList;
final AppSettings appSettings;
AppState({ AppState(
this.hasLoaded = false, {this.hasProgressesLoaded = false,
this.timeProgressList = const [], 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({ AppState copyWith({
bool hasLoaded, bool hasLoaded,
List<TimeProgress> timeProgressList, List<TimeProgress> timeProgressList,
}) { }) {
return AppState( return AppState(
hasLoaded: hasLoaded ?? this.hasLoaded, hasProgressesLoaded: hasLoaded ?? this.hasProgressesLoaded,
timeProgressList: timeProgressList ?? this.timeProgressList, timeProgressList: timeProgressList ?? this.timeProgressList,
); );
} }
@override @override
int get hashCode => hasLoaded.hashCode ^ timeProgressList.hashCode; int get hashCode => hasProgressesLoaded.hashCode ^ timeProgressList.hashCode;
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
other is AppState && other is AppState &&
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
hasLoaded == other.hasLoaded && hasProgressesLoaded == other.hasProgressesLoaded &&
timeProgressList == other.timeProgressList; timeProgressList == other.timeProgressList;
} }

View File

@ -20,26 +20,19 @@ class TimeProgress {
} }
TimeProgress copyWith( TimeProgress copyWith(
{String id, String name, DateTime startTime, DateTime endTime}) { {String id, String name, DateTime startTime, DateTime endTime}) =>
return TimeProgress( TimeProgress(
name ?? this.name, name ?? this.name,
startTime ?? this.startTime, startTime ?? this.startTime,
endTime ?? this.endTime, endTime ?? this.endTime,
id: id ?? this.id, id: id ?? this.id,
); );
}
int daysBehind() { int daysBehind() => DateTime.now().difference(startTime).inDays;
return DateTime.now().difference(startTime).inDays;
}
int daysLeft() { int daysLeft() => endTime.difference(DateTime.now()).inDays;
return endTime.difference(DateTime.now()).inDays;
}
int allDays() { int allDays() => endTime.difference(startTime).inDays;
return endTime.difference(startTime).inDays;
}
double percentDone() { double percentDone() {
double percent = this.daysBehind() / (this.allDays() / 100) / 100; double percent = this.daysBehind() / (this.allDays() / 100) / 100;
@ -48,15 +41,11 @@ class TimeProgress {
return percent; return percent;
} }
bool hasStarted() { bool hasStarted() =>
return DateTime.now().millisecondsSinceEpoch > DateTime.now().millisecondsSinceEpoch > startTime.millisecondsSinceEpoch;
startTime.millisecondsSinceEpoch;
}
bool hasEnded() { bool hasEnded() =>
return DateTime.now().millisecondsSinceEpoch > DateTime.now().millisecondsSinceEpoch > endTime.millisecondsSinceEpoch;
endTime.millisecondsSinceEpoch;
}
@override @override
int get hashCode => int get hashCode =>
@ -73,9 +62,8 @@ class TimeProgress {
endTime == other.endTime; endTime == other.endTime;
@override @override
String toString() { String toString() =>
return "TimeProgress{id: $id, name: $name, startTime: $startTime, endTime: $endTime}"; "TimeProgress{id: $id, name: $name, startTime: $startTime, endTime: $endTime}";
}
TimeProgressEntity toEntity() { TimeProgressEntity toEntity() {
if (!TimeProgress.isNameValid(name)) if (!TimeProgress.isNameValid(name))
@ -86,25 +74,17 @@ class TimeProgress {
return TimeProgressEntity(id, name, startTime, endTime); return TimeProgressEntity(id, name, startTime, endTime);
} }
static TimeProgress fromEntity(TimeProgressEntity entity) { static TimeProgress fromEntity(TimeProgressEntity entity) =>
return TimeProgress( TimeProgress(entity.name, entity.startTime, entity.endTime,
entity.name, id: entity.id ?? Uuid().generateV4());
entity.startTime,
entity.endTime,
id: entity.id ?? Uuid().generateV4(),
);
}
static bool isValid(TimeProgress tp) { static bool isValid(TimeProgress tp) =>
return TimeProgress.isNameValid(tp.name) && TimeProgress.isNameValid(tp.name) &&
TimeProgress.areTimesValid(tp.startTime, tp.endTime); TimeProgress.areTimesValid(tp.startTime, tp.endTime);
}
static bool isNameValid(String name) { static bool isNameValid(String name) =>
return name != null && name != "" && name.length > 2 && name.length < 21; name != null && name != "" && name.length > 2 && name.length < 21;
}
static bool areTimesValid(DateTime startTime, DateTime endTime) { static bool areTimesValid(DateTime startTime, DateTime endTime) =>
return startTime.isBefore(endTime); startTime.isBefore(endTime);
}
} }

View File

@ -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<AppSettingsEntity> loadAppSettings() {
final String jsonString = this.prefs.getString(_key);
if (jsonString == null)
return Future<AppSettingsEntity>.value(AppSettingsEntity.defaults());
return Future<AppSettingsEntity>.value(
AppSettingsEntity.fromJson(codec.decode(jsonString)));
}
Future<bool> 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<String, Object> toJson() =>
{_doneKey: doneColorValue, _leftKey: leftColorValue};
static AppSettingsEntity fromJson(Map<String, Object> json) =>
AppSettingsEntity(json[_doneKey], json[_leftKey]);
}

View File

@ -2,8 +2,6 @@ import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:time_progress_tracker/persistence/time_progress_entity.dart'; import 'package:time_progress_tracker/persistence/time_progress_entity.dart';
import 'dart:developer' as developer;
class TimeProgressRepository { class TimeProgressRepository {
static const String _key = "time_progress_repo"; static const String _key = "time_progress_repo";
final SharedPreferences prefs; final SharedPreferences prefs;

View File

@ -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/models/app_state.dart';
import 'package:time_progress_tracker/reducers/has_loaded_reducer.dart'; import 'package:time_progress_tracker/reducers/has_loaded_reducer.dart';
import 'package:time_progress_tracker/reducers/time_progress_list_reducer.dart'; 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(
hasLoaded: hasLoadedReducer(state.hasLoaded, action), hasProgressesLoaded: hasLoadedReducer(state.hasProgressesLoaded, action),
timeProgressList: timeProgressListReducer(state.timeProgressList, action), timeProgressList: timeProgressListReducer(state.timeProgressList, action),
appSettings: appSettingsReducers(state.appSettings, action),
); );
} }
final appSettingsReducers = combineReducers<AppSettings>([
TypedReducer<AppSettings, AppSettingsLoadedActions>(_loadAppSettings),
TypedReducer<AppSettings, UpdateAppSettingsActions>(_updateAppSettings),
TypedReducer<AppSettings, AppSettingsNotLoadedAction>(_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;

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
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';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/app_state.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';
@ -76,8 +77,17 @@ class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
List<Widget> columnChildren = [ List<Widget> columnChildren = [
Expanded( Expanded(
child: ProgressViewWidget( child: StoreConnector<AppState, AppSettings>(
timeProgress: _editMode ? _editedProgress : timeProgress), 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) if (_editMode)

View File

@ -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/app_state.dart';
import 'package:time_progress_tracker/models/time_progress.dart'; import 'package:time_progress_tracker/models/time_progress.dart';
@ -47,3 +50,9 @@ TimeProgress timeProgressByIdSelector(AppState state, String id) {
return state.timeProgressList return state.timeProgressList
.firstWhere((timeProgress) => timeProgress.id == id, orElse: () => null); .firstWhere((timeProgress) => timeProgress.id == id, orElse: () => null);
} }
AppSettings appSettingsSelector(AppState state) {
return state.appSettings;
}
Color doneColorSelector(AppState state) => state.appSettings.doneColor;

View File

@ -1,4 +1,3 @@
import 'package:flutter/cupertino.dart';
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';
@ -6,21 +5,27 @@ import 'package:time_progress_tracker/screens/progress_detail_screen.dart';
class HomeProgressListTile extends StatelessWidget { class HomeProgressListTile extends StatelessWidget {
final TimeProgress timeProgress; final TimeProgress timeProgress;
final Color doneColor;
final Color leftColor;
HomeProgressListTile({ HomeProgressListTile({
Key key,
@required this.timeProgress, @required this.timeProgress,
}) : super(key: key); @required this.doneColor,
@required this.leftColor,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget listTileSubTitle; Widget listTileSubTitle;
if (timeProgress.hasStarted() && !timeProgress.hasEnded()) if (timeProgress.hasStarted() && !timeProgress.hasEnded())
listTileSubTitle = LinearPercentIndicator( listTileSubTitle = LinearPercentIndicator(
center: Text("${(timeProgress.percentDone() * 100).floor()} %"), center: Text(
"${(timeProgress.percentDone() * 100).floor()} %",
style: TextStyle(color: Colors.white),
),
percent: timeProgress.percentDone(), percent: timeProgress.percentDone(),
progressColor: Colors.green, progressColor: doneColor,
backgroundColor: Colors.red, backgroundColor: leftColor,
lineHeight: 20, lineHeight: 20,
); );
if (!timeProgress.hasStarted()) if (!timeProgress.hasStarted())

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:time_progress_tracker/actions/actions.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/selectors/time_progress_selectors.dart';
import 'package:time_progress_tracker/widgets/home/home_progress_list_tile.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) { Widget build(BuildContext context) {
return StoreConnector( return StoreConnector(
onInit: loadTimeProgressListIfUnloaded, onInit: loadTimeProgressListIfUnloaded,
converter: (store) => store.state.hasLoaded, converter: (store) => store.state.hasProgressesLoaded,
builder: (BuildContext context, dynamic hasLoaded) { builder: (context, hasLoaded) {
if (!(hasLoaded as bool)) if (!(hasLoaded as bool))
return Center( return Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
); );
return StoreConnector( return StoreConnector<AppState, AppSettings>(
onInit: loadTimeProgressListIfUnloaded, onInit: loadSettingsIfUnloaded,
converter: (store) => activeTimeProgressesSelector(store.state), converter: (store) => appSettingsSelector(store.state),
builder: (BuildContext context, List<TimeProgress> timeProgresses) { builder: (context, AppSettings settings) {
if (timeProgresses.length < 1) if (settings == null)
return Container( return Center(child: CircularProgressIndicator());
padding: EdgeInsets.all(16), return StoreConnector<AppState, List<TimeProgress>>(
child: Center( converter: (store) => activeTimeProgressesSelector(store.state),
child: Text( builder: (context, List<TimeProgress> timeProgresses) {
"You don't have any currently active time progresses, that are tracked."), if (timeProgresses.length < 1)
), return Container(
); padding: EdgeInsets.all(16),
return ListView( child: Center(
padding: EdgeInsets.all(8), child: Text(
children: timeProgresses "You don't have any currently active time progresses, that are tracked."),
.map((timeProgress) => HomeProgressListTile( ),
timeProgress: timeProgress, );
)) return ListView(
.toList(), padding: EdgeInsets.all(8),
children: timeProgresses
.map((timeProgress) => HomeProgressListTile(
timeProgress: timeProgress,
doneColor: settings.doneColor,
leftColor: settings.leftColor,
))
.toList(),
);
},
); );
}, },
); );

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:time_progress_tracker/actions/actions.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/selectors/time_progress_selectors.dart';
import 'package:time_progress_tracker/widgets/home/home_progress_list_tile.dart'; import 'package:time_progress_tracker/widgets/home/home_progress_list_tile.dart';
@ -9,31 +10,41 @@ class HomeInactiveProgressesTab extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StoreConnector( return StoreConnector(
onInit: loadTimeProgressListIfUnloaded, onInit: loadSettingsIfUnloaded,
converter: (store) => store.state.hasLoaded, converter: (store) => appSettingsSelector(store.state),
builder: (BuildContext context, dynamic hasLoaded) { builder: (context, AppSettings settings) {
if (!(hasLoaded as bool))
return Center(
child: CircularProgressIndicator(),
);
return StoreConnector( return StoreConnector(
onInit: loadTimeProgressListIfUnloaded, onInit: loadTimeProgressListIfUnloaded,
converter: (store) => inactiveTimeProgressesSelector(store.state), converter: (store) => store.state.hasProgressesLoaded,
builder: (BuildContext context, List<TimeProgress> timeProgresses) { builder: (BuildContext context, dynamic hasLoaded) {
if (timeProgresses.length < 1) if (!(hasLoaded as bool))
return Container( return Center(
padding: EdgeInsets.all(16), child: CircularProgressIndicator(),
child: Center(
child: Text(
"You don't have any currently inactive time progresses, that are tracked."),
),
); );
return ListView( return StoreConnector(
padding: EdgeInsets.all(8), onInit: loadTimeProgressListIfUnloaded,
children: timeProgresses converter: (store) => inactiveTimeProgressesSelector(store.state),
.map((timeProgress) => builder:
HomeProgressListTile(timeProgress: timeProgress)) (BuildContext context, List<TimeProgress> timeProgresses) {
.toList(), 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,28 +1,62 @@
import 'package:flutter/material.dart'; 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/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 { class HomeSettingsTab extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return StoreConnector<AppState, AppSettings>(
padding: EdgeInsets.all(16), onInit: loadSettingsIfUnloaded,
child: Center( converter: (store) => appSettingsSelector(store.state),
child: Column( builder: (context, AppSettings settings) {
children: [ Store<AppState> store = StoreProvider.of<AppState>(context);
Text("The Settings of this App are not yet implemented."), void updateDoneColor(Color newDoneColor) => store.dispatch(
FlatButton( UpdateAppSettingsActions(
onPressed: () { settings.copyWith(doneColor: newDoneColor)),
showAboutDialog( );
context: context, void updateLeftColor(Color newLeftColor) => store.dispatch(
applicationName: TimeProgressTrackerApp.name, UpdateAppSettingsActions(
applicationVersion: "Beta", settings.copyWith(leftColor: newLeftColor)),
applicationLegalese: '\u00a9Andreas Fahrecker 2020-2021'); );
},
child: Text("About")) 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"),
),
),
],
),
),
);
},
); );
} }
} }

View File

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

View File

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

View File

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

View File

@ -5,9 +5,13 @@ import 'package:time_progress_tracker/models/time_progress.dart';
class ProgressViewWidget extends StatelessWidget { class ProgressViewWidget extends StatelessWidget {
final TimeProgress timeProgress; final TimeProgress timeProgress;
final Color doneColor;
final Color leftColor;
ProgressViewWidget({ ProgressViewWidget({
@required this.timeProgress, @required this.timeProgress,
@required this.doneColor,
@required this.leftColor,
}); });
@override @override
@ -33,8 +37,8 @@ class ProgressViewWidget extends StatelessWidget {
radius: 100, radius: 100,
lineWidth: 10, lineWidth: 10,
percent: timeProgress.percentDone(), percent: timeProgress.percentDone(),
progressColor: Colors.green, progressColor: doneColor,
backgroundColor: Colors.red, backgroundColor: leftColor,
center: Text("${(timeProgress.percentDone() * 100).floor()} %"), center: Text("${(timeProgress.percentDone() * 100).floor()} %"),
), ),
), ),
@ -43,10 +47,13 @@ class ProgressViewWidget extends StatelessWidget {
padding: EdgeInsets.symmetric(horizontal: 15), padding: EdgeInsets.symmetric(horizontal: 15),
percent: timeProgress.percentDone(), percent: timeProgress.percentDone(),
leading: Text("${timeProgress.daysBehind()} Days"), 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"), trailing: Text("${timeProgress.daysLeft()} Days"),
progressColor: Colors.green, progressColor: doneColor,
backgroundColor: Colors.red, backgroundColor: leftColor,
lineHeight: 25, lineHeight: 25,
), ),
), ),

View File

@ -78,6 +78,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.3" 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: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -104,6 +111,13 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: 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. # 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.14+14 version: 0.0.17+17
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: ">=2.7.0 <3.0.0"
@ -23,7 +23,9 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_colorpicker:
flutter_redux: flutter_redux:
direct_select:
meta: meta:
package_info: package_info:
percent_indicator: percent_indicator: