From f013c0de6508b64ce13174e0dd426ab84c0ae4b5 Mon Sep 17 00:00:00 2001 From: Andreas Fahrecker Date: Fri, 20 Nov 2020 01:17:17 +0100 Subject: [PATCH] Feature/basic app (#1) Feature/Basic App (#1) Basic App Structure Consists of Time Progress Dashboard, Time Progress Detail View and Time Progress Creator. All of these have an AppDrawer with a Link To the Dashboard and all your Track Time Progresses, also an About Button. Commits: * Undetailed Commit more work * Changed isEditing ? in Detail Screen and Extracted FAB row to widget * Extracted Progress Detail Fab Row and Progress Detail select Date Btn to widgets * Create Progress Detail Widgets Folder * Extracted Edit Dates Row Widget * Extracted Functions from ui * Made some fields private * LoadTimerProgressList if unloaded function * Created App Yes No Dialog Widget * Using Yes No Dialog in Detail Screen * Created TimeProgress Initial Default factory * Renamed to Time Progress Tracker * Added About Button in App Drawer * Code cleanup * Code clean up and fixed Bug with null as string in Repository Signed-off-by Andreas Fahrecker --- README.md | 2 +- lib/actions/actions.dart | 10 +- lib/app.dart | 31 ++- lib/main.dart | 12 +- .../store_time_progress_middleware.dart | 29 ++- lib/models/app_state.dart | 15 +- lib/models/time_progress.dart | 41 ++- lib/persistence/time_progress_entity.dart | 13 +- lib/persistence/time_progress_repository.dart | 16 +- lib/reducers/app_state_reducer.dart | 10 +- lib/reducers/has_loaded_reducer.dart | 15 ++ lib/reducers/time_progress_list_reducer.dart | 4 +- lib/screens/progress_creation_screen.dart | 143 +++++++++++ lib/screens/progress_dashboard_screen.dart | 94 +++++++ lib/screens/progress_detail_screen.dart | 239 ++++++++++++++++++ lib/screens/progress_screen.dart | 190 -------------- lib/selectors/time_progress_selectors.dart | 10 +- lib/widgets/app_drawer_widget.dart | 110 ++++++++ lib/widgets/app_yes_no_dialog_widget.dart | 34 +++ ...ogress_detail_circular_percent_widget.dart | 21 ++ ...progress_detail_edit_dates_row_widget.dart | 44 ++++ ...rogress_detail_fab_editing_row_widget.dart | 35 +++ .../progress_detail_fab_row_widget.dart | 33 +++ ...progress_detail_linear_percent_widget.dart | 24 ++ ...rogress_detail_select_date_btn_widget.dart | 32 +++ pubspec.lock | 58 ++--- pubspec.yaml | 2 +- 27 files changed, 984 insertions(+), 283 deletions(-) create mode 100644 lib/reducers/has_loaded_reducer.dart create mode 100644 lib/screens/progress_creation_screen.dart create mode 100644 lib/screens/progress_dashboard_screen.dart create mode 100644 lib/screens/progress_detail_screen.dart delete mode 100644 lib/screens/progress_screen.dart create mode 100644 lib/widgets/app_drawer_widget.dart create mode 100644 lib/widgets/app_yes_no_dialog_widget.dart create mode 100644 lib/widgets/progress_detail_widgets/progress_detail_circular_percent_widget.dart create mode 100644 lib/widgets/progress_detail_widgets/progress_detail_edit_dates_row_widget.dart create mode 100644 lib/widgets/progress_detail_widgets/progress_detail_fab_editing_row_widget.dart create mode 100644 lib/widgets/progress_detail_widgets/progress_detail_fab_row_widget.dart create mode 100644 lib/widgets/progress_detail_widgets/progress_detail_linear_percent_widget.dart create mode 100644 lib/widgets/progress_detail_widgets/progress_detail_select_date_btn_widget.dart diff --git a/README.md b/README.md index 7b45dbc..8334efa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# time_progress_calculator +# time_progress_tracker A Flutter Application to create Timers with a percentage indicator. diff --git a/lib/actions/actions.dart b/lib/actions/actions.dart index 27dcfe9..8a6975d 100644 --- a/lib/actions/actions.dart +++ b/lib/actions/actions.dart @@ -1,4 +1,6 @@ -import 'package:time_progress_calculator/models/time_progress.dart'; +import 'package:redux/redux.dart'; +import 'package:time_progress_tracker/models/app_state.dart'; +import 'package:time_progress_tracker/models/time_progress.dart'; class LoadTimeProgressListAction {} @@ -28,3 +30,9 @@ class DeleteTimeProgressAction { DeleteTimeProgressAction(this.id); } + +void loadTimeProgressListIfUnloaded(Store store) { + if (!store.state.hasLoaded) { + store.dispatch(LoadTimeProgressListAction()); + } +} diff --git a/lib/app.dart b/lib/app.dart index 0c0dc2e..6772be1 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,29 +1,36 @@ import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:redux/redux.dart'; -import 'package:time_progress_calculator/models/app_state.dart'; -import 'package:time_progress_calculator/screens/progress_screen.dart'; +import 'package:time_progress_tracker/models/app_state.dart'; +import 'package:time_progress_tracker/screens/progress_creation_screen.dart'; +import 'package:time_progress_tracker/screens/progress_dashboard_screen.dart'; +import 'package:time_progress_tracker/screens/progress_detail_screen.dart'; + +class TimeProgressTrackerApp extends StatelessWidget { + static const String name = "Time Progress Tracker"; -class TimeProgressCalculatorApp extends StatelessWidget { final Store store; - TimeProgressCalculatorApp({Key key, this.store}) : super(key: key); + TimeProgressTrackerApp({Key key, this.store}) : super(key: key); @override Widget build(BuildContext context) { return StoreProvider( store: store, child: MaterialApp( - title: "Time Progress Calculator", + title: name, theme: ThemeData( - primarySwatch: Colors.blue, - visualDensity: VisualDensity.adaptivePlatformDensity), - initialRoute: "/", + primarySwatch: Colors.blue, + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + initialRoute: ProgressDashboardScreen.routeName, routes: { - "/": (BuildContext context) => ProgressScreen( - name: "Zivildienst", - context: context, - ) + ProgressDashboardScreen.routeName: (BuildContext context) => + ProgressDashboardScreen(), + ProgressDetailScreen.routeName: (BuildContext context) => + ProgressDetailScreen(), + ProgressCreationScreen.routeName: (BuildContext context) => + ProgressCreationScreen(), }, ), ); diff --git a/lib/main.dart b/lib/main.dart index 5a43827..db4686f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:redux/redux.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:time_progress_calculator/app.dart'; -import 'package:time_progress_calculator/middleware/store_time_progress_middleware.dart'; -import 'package:time_progress_calculator/models/app_state.dart'; -import 'package:time_progress_calculator/persistence/time_progress_repository.dart'; -import 'package:time_progress_calculator/reducers/app_state_reducer.dart'; +import 'package:time_progress_tracker/app.dart'; +import 'package:time_progress_tracker/middleware/store_time_progress_middleware.dart'; +import 'package:time_progress_tracker/models/app_state.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(); - runApp(TimeProgressCalculatorApp( + runApp(TimeProgressTrackerApp( store: Store( appStateReducer, initialState: AppState.initial(), diff --git a/lib/middleware/store_time_progress_middleware.dart b/lib/middleware/store_time_progress_middleware.dart index 9cd207c..5aa9b54 100644 --- a/lib/middleware/store_time_progress_middleware.dart +++ b/lib/middleware/store_time_progress_middleware.dart @@ -1,10 +1,10 @@ import 'package:redux/redux.dart'; -import 'package:time_progress_calculator/actions/actions.dart'; -import 'package:time_progress_calculator/models/app_state.dart'; -import 'package:time_progress_calculator/models/time_progress.dart'; -import 'package:time_progress_calculator/persistence/time_progress_entity.dart'; -import 'package:time_progress_calculator/persistence/time_progress_repository.dart'; -import 'package:time_progress_calculator/selectors/time_progress_selectors.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 '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) { @@ -19,7 +19,8 @@ List> createStoreTimeProgressListMiddleware( ]; } -Middleware _createSaveTimeProgressList(TimeProgressRepository repository) { +Middleware _createSaveTimeProgressList( + TimeProgressRepository repository) { return (Store store, dynamic action, NextDispatcher next) { next(action); @@ -31,12 +32,18 @@ Middleware _createSaveTimeProgressList(TimeProgressRepository reposito }; } -Middleware _createLoadTimeProgressList(TimeProgressRepository repository) { +Middleware _createLoadTimeProgressList( + TimeProgressRepository repository) { return (Store store, dynamic action, NextDispatcher next) { repository.loadTimeProgressList().then((timeProgresses) { - store.dispatch( - TimeProgressListLoadedAction(timeProgresses.map(TimeProgress.fromEntity).toList()), - ); + List timeProgressList = + timeProgresses.map(TimeProgress.fromEntity).toList(); + if (timeProgressList == null) { + timeProgressList = []; + } + store.dispatch(TimeProgressListLoadedAction( + timeProgressList, + )); }).catchError((_) => store.dispatch(TimeProgressListNotLoadedAction())); }; } diff --git a/lib/models/app_state.dart b/lib/models/app_state.dart index 1876cc5..b436b7e 100644 --- a/lib/models/app_state.dart +++ b/lib/models/app_state.dart @@ -1,35 +1,36 @@ import 'package:meta/meta.dart'; -import 'package:time_progress_calculator/models/time_progress.dart'; +import 'package:time_progress_tracker/models/time_progress.dart'; @immutable class AppState { - final bool isLoading; + final bool hasLoaded; final List timeProgressList; AppState({ - this.isLoading = false, + this.hasLoaded = false, this.timeProgressList = const [], }); - factory AppState.initial() => AppState(isLoading: true); + factory AppState.initial() => AppState(hasLoaded: false); AppState copyWith({ - bool isLoading, + bool hasLoaded, List timeProgressList, }) { return AppState( - isLoading: isLoading ?? this.isLoading, + hasLoaded: hasLoaded ?? this.hasLoaded, timeProgressList: timeProgressList ?? this.timeProgressList, ); } @override - int get hashCode => timeProgressList.hashCode; + int get hashCode => hasLoaded.hashCode ^ timeProgressList.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is AppState && runtimeType == other.runtimeType && + hasLoaded == other.hasLoaded && timeProgressList == other.timeProgressList; } diff --git a/lib/models/time_progress.dart b/lib/models/time_progress.dart index f9fc88a..ebde421 100644 --- a/lib/models/time_progress.dart +++ b/lib/models/time_progress.dart @@ -1,26 +1,51 @@ import 'package:meta/meta.dart'; -import 'package:time_progress_calculator/persistence/time_progress_entity.dart'; -import 'package:time_progress_calculator/uuid.dart'; +import 'package:time_progress_tracker/persistence/time_progress_entity.dart'; +import 'package:time_progress_tracker/uuid.dart'; @immutable class TimeProgress { final String id; + final String name; final DateTime startTime; final DateTime endTime; - TimeProgress(this.startTime, this.endTime, {String id}) + TimeProgress(this.name, this.startTime, this.endTime, {String id}) : id = id ?? Uuid().generateV4(); - TimeProgress copyWith({String id, DateTime startTime, DateTime endTime}) { + factory TimeProgress.initialDefault() { + int thisYear = DateTime.now().year; + return TimeProgress("", DateTime(thisYear - 1), DateTime(thisYear + 1)); + } + + 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, ); } + int daysBehind() { + return DateTime.now().difference(startTime).inDays; + } + + int daysLeft() { + return endTime.difference(DateTime.now()).inDays; + } + + int allDays() { + return endTime.difference(startTime).inDays; + } + + double percentDone() { + return this.daysBehind() / (this.allDays() / 100) / 100; + } + @override - int get hashCode => id.hashCode ^ startTime.hashCode ^ endTime.hashCode; + int get hashCode => + id.hashCode ^ name.hashCode ^ startTime.hashCode ^ endTime.hashCode; @override bool operator ==(Object other) => @@ -28,20 +53,22 @@ class TimeProgress { other is TimeProgress && runtimeType == other.runtimeType && id == other.id && + name == other.name && startTime == other.startTime && endTime == other.endTime; @override String toString() { - return "Timer{id: $id, startTimer: $startTime, endTimer: $endTime}"; + return "TimeProgress{id: $id, name: $name, startTime: $startTime, endTime: $endTime}"; } TimeProgressEntity toEntity() { - return TimeProgressEntity(id, startTime, endTime); + return TimeProgressEntity(id, name, startTime, endTime); } static TimeProgress fromEntity(TimeProgressEntity entity) { return TimeProgress( + entity.name, entity.startTime, entity.endTime, id: entity.id ?? Uuid().generateV4(), diff --git a/lib/persistence/time_progress_entity.dart b/lib/persistence/time_progress_entity.dart index 6fde489..ebdfc6c 100644 --- a/lib/persistence/time_progress_entity.dart +++ b/lib/persistence/time_progress_entity.dart @@ -1,12 +1,14 @@ class TimeProgressEntity { final String id; + final String name; final DateTime startTime; final DateTime endTime; - TimeProgressEntity(this.id, this.startTime, this.endTime); + TimeProgressEntity(this.id, this.name, this.startTime, this.endTime); @override - int get hashCode => id.hashCode ^ startTime.hashCode ^ endTime.hashCode; + int get hashCode => + id.hashCode ^ name.hashCode ^ startTime.hashCode ^ endTime.hashCode; @override bool operator ==(Object other) => @@ -14,23 +16,26 @@ class TimeProgressEntity { other is TimeProgressEntity && runtimeType == other.runtimeType && id == other.id && + name == other.name && startTime == other.startTime && endTime == other.endTime; Map toJson() { return { "id": id, + "name": name, "startTime": startTime.millisecondsSinceEpoch, - "endTime": startTime.millisecondsSinceEpoch + "endTime": endTime.millisecondsSinceEpoch }; } static TimeProgressEntity fromJson(Map json) { final String id = json["id"] as String; + final String name = json["name"] as String; final DateTime startTime = DateTime.fromMillisecondsSinceEpoch(json["startTime"] as int); final DateTime endTime = DateTime.fromMillisecondsSinceEpoch(json["endTime"] as int); - return TimeProgressEntity(id, startTime, endTime); + return TimeProgressEntity(id, name, startTime, endTime); } } diff --git a/lib/persistence/time_progress_repository.dart b/lib/persistence/time_progress_repository.dart index 59babd9..1fb8765 100644 --- a/lib/persistence/time_progress_repository.dart +++ b/lib/persistence/time_progress_repository.dart @@ -1,7 +1,8 @@ import 'dart:convert'; - import 'package:shared_preferences/shared_preferences.dart'; -import 'package:time_progress_calculator/persistence/time_progress_entity.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"; @@ -12,16 +13,19 @@ class TimeProgressRepository { Future> loadTimeProgressList() { final String jsonString = this.prefs.getString(_key); - return codec + if (jsonString == null) { + return Future>.value([]); + } + return Future>.value(codec .decode(jsonString)["timers"] .cast>() .map(TimeProgressEntity.fromJson) - .toList(growable: false); + .toList(growable: false)); } Future saveTimeProgressList(List timeProgressList) { - final String jsonString = codec - .encode({"timers": timeProgressList.map((timer) => timer.toJson()).toList()}); + final String jsonString = codec.encode( + {"timers": timeProgressList.map((timer) => timer.toJson()).toList()}); return this.prefs.setString(_key, jsonString); } } diff --git a/lib/reducers/app_state_reducer.dart b/lib/reducers/app_state_reducer.dart index 729f4b3..b838eb6 100644 --- a/lib/reducers/app_state_reducer.dart +++ b/lib/reducers/app_state_reducer.dart @@ -1,6 +1,10 @@ -import 'package:time_progress_calculator/models/app_state.dart'; -import 'package:time_progress_calculator/reducers/time_progress_list_reducer.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(timeProgressList: timeProgressListReducer(state.timeProgressList, action)); + return AppState( + hasLoaded: hasLoadedReducer(state.hasLoaded, action), + timeProgressList: timeProgressListReducer(state.timeProgressList, action), + ); } diff --git a/lib/reducers/has_loaded_reducer.dart b/lib/reducers/has_loaded_reducer.dart new file mode 100644 index 0000000..cb7ab0c --- /dev/null +++ b/lib/reducers/has_loaded_reducer.dart @@ -0,0 +1,15 @@ +import 'package:redux/redux.dart'; +import 'package:time_progress_tracker/actions/actions.dart'; + +final hasLoadedReducer = combineReducers([ + TypedReducer(_setLoaded), + TypedReducer(_setUnloaded) +]); + +bool _setLoaded(bool hasLoaded, TimeProgressListLoadedAction action) { + return true; +} + +bool _setUnloaded(bool hasLoaded, TimeProgressListNotLoadedAction action) { + return false; +} diff --git a/lib/reducers/time_progress_list_reducer.dart b/lib/reducers/time_progress_list_reducer.dart index 6f9c38d..7e643a4 100644 --- a/lib/reducers/time_progress_list_reducer.dart +++ b/lib/reducers/time_progress_list_reducer.dart @@ -1,6 +1,6 @@ -import 'package:time_progress_calculator/actions/actions.dart'; -import 'package:time_progress_calculator/models/time_progress.dart'; import 'package:redux/redux.dart'; +import 'package:time_progress_tracker/actions/actions.dart'; +import 'package:time_progress_tracker/models/time_progress.dart'; final timeProgressListReducer = combineReducers>([ TypedReducer, TimeProgressListLoadedAction>( diff --git a/lib/screens/progress_creation_screen.dart b/lib/screens/progress_creation_screen.dart new file mode 100644 index 0000000..07b801a --- /dev/null +++ b/lib/screens/progress_creation_screen.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_redux/flutter_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 'package:time_progress_tracker/screens/progress_dashboard_screen.dart'; +import 'package:time_progress_tracker/widgets/app_drawer_widget.dart'; + +class ProgressCreationScreen extends StatefulWidget { + static const routeName = "/progress-creation"; + + @override + State createState() { + return _ProgressCreationScreenState(); + } +} + +class _ProgressCreationScreenState extends State { + final TextEditingController _nameController = TextEditingController(); + DateTime pickedStartTime = DateTime.now(); + DateTime pickedEndTime = DateTime( + DateTime.now().year + 1, DateTime.now().month, DateTime.now().day); + + Future _selectDate( + BuildContext context, DateTime initialDate) async { + return await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: DateTime(DateTime.now().year - 5), + lastDate: DateTime(DateTime.now().year + 5)); + } + + void _createTimeProgress(BuildContext context) { + StoreProvider.of(context).dispatch(AddTimeProgressAction( + TimeProgress(_nameController.text, pickedStartTime, pickedEndTime), + )); + Navigator.pushNamed(context, ProgressDashboardScreen.routeName); + } + + @override + void dispose() { + _nameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Create Time Progress"), + ), + drawer: AppDrawer(), + body: Container( + padding: EdgeInsets.all(8), + child: Column( + children: [ + Expanded( + flex: 1, + child: TextField( + controller: _nameController, + decoration: InputDecoration( + border: OutlineInputBorder(), labelText: "Progress Name"), + ), + ), + Expanded( + child: Text("${_nameController.text}"), + ), + Expanded( + flex: 1, + child: Row( + children: [ + Expanded( + flex: 5, + child: FlatButton( + color: Colors.blue, + child: Text( + "Start Date: ${pickedStartTime.toLocal().toString().split(" ")[0]}"), + onPressed: () async { + DateTime dt = + await _selectDate(context, pickedStartTime); + if (dt != null) { + setState(() { + pickedStartTime = dt; + }); + } + }, + ), + ), + Spacer( + flex: 1, + ), + Expanded( + flex: 5, + child: FlatButton( + color: Colors.blue, + child: Text( + "End Date: ${pickedEndTime.toLocal().toString().split(" ")[0]}"), + onPressed: () async { + DateTime dt = await _selectDate(context, pickedEndTime); + if (dt != null) { + setState(() { + pickedEndTime = dt; + }); + } + }, + ), + ), + ], + ), + ), + Spacer( + flex: 5, + ) + ], + ), + ), + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: Row( + children: [ + Expanded( + child: FloatingActionButton( + heroTag: "createTimeProgressBTN", + child: Icon(Icons.save), + onPressed: () { + _createTimeProgress(context); + }, + ), + ), + Expanded( + child: FloatingActionButton( + heroTag: "cancelTimeProgressCreationBTN", + child: Icon(Icons.cancel), + onPressed: () { + Navigator.pushNamed(context, ProgressDashboardScreen.routeName); + }, + ), + ) + ], + ), + ); + } +} diff --git a/lib/screens/progress_dashboard_screen.dart b/lib/screens/progress_dashboard_screen.dart new file mode 100644 index 0000000..ce8dfc2 --- /dev/null +++ b/lib/screens/progress_dashboard_screen.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:percent_indicator/linear_percent_indicator.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 'package:time_progress_tracker/screens/progress_creation_screen.dart'; +import 'package:time_progress_tracker/screens/progress_detail_screen.dart'; +import 'package:time_progress_tracker/widgets/app_drawer_widget.dart'; + +class ProgressDashboardScreen extends StatelessWidget { + static const routeName = "/progress-dashboard"; + static const title = "Time Progress Dashboard"; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(title), + ), + drawer: AppDrawer(), + body: StoreConnector( + converter: _ViewModel.fromStore, + onInit: loadTimeProgressListIfUnloaded, + builder: (BuildContext context, _ViewModel vm) { + if (!vm.hasLoaded) { + return Center( + child: CircularProgressIndicator(), + ); + } + List dashboardTileList = List(); + + if (vm.timeProgressList.length > 0) { + for (TimeProgress tp in vm.timeProgressList) { + dashboardTileList.add( + Card( + child: ListTile( + title: Text(tp.name), + subtitle: LinearPercentIndicator( + center: Text("${(tp.percentDone() * 100).floor()} %"), + percent: tp.percentDone(), + progressColor: Colors.green, + backgroundColor: Colors.red, + lineHeight: 20, + ), + onTap: () { + Navigator.pushNamed( + context, ProgressDetailScreen.routeName, + arguments: ProgressDetailScreenArguments(tp.id)); + }, + ), + ), + ); + } + } else { + dashboardTileList.add(ListTile( + title: Text("You don't have any tracked Progress."), + )); + } + + return ListView( + padding: EdgeInsets.all(8), + children: dashboardTileList, + ); + }, + ), + floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, + floatingActionButton: FloatingActionButton( + child: Icon(Icons.add), + onPressed: () { + Navigator.pushNamed(context, ProgressCreationScreen.routeName); + }, + ), + ); + } +} + +class _ViewModel { + final List timeProgressList; + final bool hasLoaded; + + _ViewModel({ + @required this.timeProgressList, + @required this.hasLoaded, + }); + + static _ViewModel fromStore(Store store) { + return _ViewModel( + timeProgressList: store.state.timeProgressList, + hasLoaded: store.state.hasLoaded, + ); + } +} diff --git a/lib/screens/progress_detail_screen.dart b/lib/screens/progress_detail_screen.dart new file mode 100644 index 0000000..206fdf3 --- /dev/null +++ b/lib/screens/progress_detail_screen.dart @@ -0,0 +1,239 @@ +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 'package:time_progress_tracker/screens/progress_dashboard_screen.dart'; +import 'package:time_progress_tracker/selectors/time_progress_selectors.dart'; +import 'package:time_progress_tracker/widgets/app_drawer_widget.dart'; +import 'package:time_progress_tracker/widgets/app_yes_no_dialog_widget.dart'; +import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_circular_percent_widget.dart'; +import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_edit_dates_row_widget.dart'; +import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_fab_editing_row_widget.dart'; +import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_fab_row_widget.dart'; +import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_linear_percent_widget.dart'; + +class ProgressDetailScreenArguments { + final String id; + + ProgressDetailScreenArguments(this.id); +} + +class ProgressDetailScreen extends StatefulWidget { + static const routeName = "/progress-detail"; + + @override + State createState() { + return _ProgressDetailScreenState(); + } +} + +class _ProgressDetailScreenState extends State { + final TextEditingController _nameController = TextEditingController(); + bool _isBeingEdited = false; + TimeProgress _editedProgress = TimeProgress.initialDefault(); + + void _onStartDateChanged(DateTime picked) { + if (picked != null) { + setState(() { + _editedProgress = _editedProgress.copyWith(startTime: picked); + }); + } + } + + void _onEndDateChanged(DateTime picked) { + if (picked != null) { + setState(() { + _editedProgress = _editedProgress.copyWith(endTime: picked); + }); + } + } + + void _onSaveTimeProgress(Store store, id) { + store.dispatch(UpdateTimeProgressAction(id, _editedProgress)); + setState(() { + _isBeingEdited = false; + }); + } + + void _showCancelEditTimeProgressDialog(AppState state, id) { + TimeProgress originalTp = timeProgressByIdSelector(state, id); + if (originalTp != _editedProgress) { + String originalName = timeProgressByIdSelector(state, id).name; + showDialog( + context: context, + builder: (_) => AppYesNoDialog( + titleText: "Cancel Editing of $originalName", + contentText: + "Are you sure that you want to discard the changes done to $originalName", + onYesPressed: _onCancelEditTimeProgress, + onNoPressed: _onCloseDialog, + ), + ); + } else { + setState(() { + _isBeingEdited = false; + }); + } + } + + void _onCancelEditTimeProgress() { + setState(() { + _isBeingEdited = false; + }); + Navigator.pop(context); + } + + void _onEditTimeProgress(Store store, id) { + setState(() { + _isBeingEdited = true; + _editedProgress = timeProgressByIdSelector(store.state, id); + _nameController.text = _editedProgress.name; + }); + } + + void _showDeleteTimeProgressDialog(Store store, id) { + showDialog( + context: context, + builder: (_) => AppYesNoDialog( + titleText: "Delete ${timeProgressByIdSelector(store.state, id).name}", + contentText: "Are you sure you want to delete this time progress?", + onYesPressed: () => _onDeleteTimeProgress(store, id), + onNoPressed: _onCloseDialog, + ), + ); + } + + void _onDeleteTimeProgress(Store store, String id) { + store.dispatch(DeleteTimeProgressAction(id)); + Navigator.popAndPushNamed(context, ProgressDashboardScreen.routeName); + } + + void _onCloseDialog() { + Navigator.pop(context); + } + + @override + void initState() { + super.initState(); + _nameController.addListener(() { + this.setState(() { + this._editedProgress = + this._editedProgress.copyWith(name: _nameController.text); + }); + }); + } + + @override + Widget build(BuildContext context) { + final ProgressDetailScreenArguments args = + ModalRoute.of(context).settings.arguments; + final Store store = StoreProvider.of(context); + + return Scaffold( + appBar: AppBar( + title: Text("Progress"), + ), + drawer: AppDrawer(), + body: Container( + margin: EdgeInsets.all(8), + child: StoreConnector( + converter: (Store store) => + _ViewModel.fromStoreAndArg(store, args), + onInit: loadTimeProgressListIfUnloaded, + builder: (BuildContext context, _ViewModel vm) { + return Column( + children: [ + Expanded( + flex: 1, + child: _isBeingEdited + ? TextField( + controller: _nameController, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: "Progress Name"), + ) + : FittedBox( + fit: BoxFit.fitWidth, + child: Text( + vm.timeProgress.name, + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + ), + ), + Expanded( + flex: 2, + child: ProgressDetailCircularPercent( + percentDone: _isBeingEdited + ? _editedProgress.percentDone() + : vm.timeProgress.percentDone(), + ), + ), + Expanded( + flex: 1, + child: ProgressDetailLinearPercent( + timeProgress: + _isBeingEdited ? _editedProgress : vm.timeProgress, + ), + ), + Expanded( + flex: 1, + child: Text( + "${_isBeingEdited ? _editedProgress.allDays() : vm.timeProgress.allDays()} Days"), + ), + this._isBeingEdited + ? Expanded( + flex: 1, + child: ProgressDetailEditDatesRow( + startTime: _editedProgress.startTime, + endTime: _editedProgress.endTime, + onStartTimeChanged: _onStartDateChanged, + onEndTimeChanged: _onEndDateChanged, + ), + ) + : Spacer(flex: 1), + Spacer(flex: 1) + ], + ); + }, + ), + ), + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: _isBeingEdited + ? ProgressDetailFabEditingRow( + onSave: () => _onSaveTimeProgress(store, args.id), + onCancelEdit: () => + _showCancelEditTimeProgressDialog(store.state, args.id), + ) + : ProgressDetailFabRow( + onEdit: () => _onEditTimeProgress(store, args.id), + onDelete: () => _showDeleteTimeProgressDialog(store, args.id), + ), + ); + } + + @override + void dispose() { + _nameController.dispose(); + super.dispose(); + } +} + +class _ViewModel { + final TimeProgress timeProgress; + + _ViewModel({ + @required this.timeProgress, + }); + + static _ViewModel fromStoreAndArg( + Store store, ProgressDetailScreenArguments args) { + return _ViewModel( + timeProgress: timeProgressByIdSelector(store.state, args.id), + ); + } +} diff --git a/lib/screens/progress_screen.dart b/lib/screens/progress_screen.dart deleted file mode 100644 index ddc6b4d..0000000 --- a/lib/screens/progress_screen.dart +++ /dev/null @@ -1,190 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_redux/flutter_redux.dart'; -import 'package:percent_indicator/circular_percent_indicator.dart'; -import 'package:percent_indicator/linear_percent_indicator.dart'; -import 'package:redux/redux.dart'; -import 'package:time_progress_calculator/actions/actions.dart'; -import 'package:time_progress_calculator/models/app_state.dart'; -import 'package:time_progress_calculator/models/time_progress.dart'; - -class ProgressScreen extends StatefulWidget { - const ProgressScreen({Key key, @required this.context, this.name}) - : super(key: key); - - final BuildContext context; - final String name; - - @override - State createState() { - return _ProgressScreenState(); - } -} - -class _ProgressScreenState extends State { - void _selectStartDate(BuildContext context) async { - Store store = StoreProvider.of(context); - final DateTime picked = await showDatePicker( - context: context, - initialDate: store.state.timeProgressList[0].startTime, - firstDate: DateTime(2000), - lastDate: DateTime(2100)); - if (picked != null && picked != store.state.timeProgressList[0].startTime) { - store.dispatch(UpdateTimeProgressAction( - store.state.timeProgressList[0].id, - store.state.timeProgressList[0].copyWith(startTime: picked), - )); - } - } - - void _selectEndDate(BuildContext context) async { - Store store = StoreProvider.of(context); - final DateTime picked = await showDatePicker( - context: context, - initialDate: store.state.timeProgressList[0].endTime, - firstDate: DateTime(2000), - lastDate: DateTime(2100)); - if (picked != null && picked != store.state.timeProgressList[0].endTime) { - store.dispatch(UpdateTimeProgressAction( - store.state.timeProgressList[0].id, - store.state.timeProgressList[0].copyWith(endTime: picked), - )); - } - } - - @override - void initState() { - super.initState(); - if (StoreProvider.of(widget.context).state.timeProgressList.length < 1) { - StoreProvider.of(widget.context).dispatch(AddTimeProgressAction(TimeProgress( - DateTime(2000), - DateTime(2100), - ))); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("${widget.name} Progress"), - ), - body: StoreConnector( - converter: _ViewModel.fromStore, - builder: (context, _ViewModel vm) { - final int daysDone = - DateTime.now().difference(vm.timeProgress.startTime).inDays; - final int daysLeft = - vm.timeProgress.endTime.difference(DateTime.now()).inDays; - final int allDays = - vm.timeProgress.endTime.difference(vm.timeProgress.startTime).inDays; - final double percent = daysDone / (allDays / 100) / 100; - - return Container( - margin: const EdgeInsets.all(5), - child: Column( - children: [ - Expanded( - flex: 1, - child: Row( - children: [ - Expanded( - flex: 2, - child: Text( - "Start Date:", - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "${vm.timeProgress.startTime.toLocal()}".split(" ")[0]), - ), - Expanded( - flex: 2, - child: RaisedButton( - onPressed: () => _selectStartDate(context), - child: Text("Change"), - color: Colors.lightBlueAccent, - ), - ), - Spacer(flex: 1) - ], - ), - ), - Expanded( - flex: 1, - child: Row( - children: [ - Expanded( - flex: 2, - child: Text( - "End Date:", - textAlign: TextAlign.right, - ), - ), - Expanded( - flex: 2, - child: Text( - "${vm.timeProgress.endTime.toLocal()}".split(" ")[0]), - ), - Expanded( - flex: 2, - child: RaisedButton( - onPressed: () => _selectEndDate(context), - child: Text("Change"), - color: Colors.lightBlueAccent, - ), - ), - Spacer(flex: 1) - ], - ), - ), - Expanded( - flex: 5, - child: CircularPercentIndicator( - radius: 100, - lineWidth: 10, - percent: percent, - progressColor: Colors.green, - backgroundColor: Colors.red, - center: Text("${(percent * 100).floor()} %"), - ), - ), - Expanded( - flex: 1, - child: LinearPercentIndicator( - padding: EdgeInsets.symmetric(horizontal: 15.0), - percent: percent, - leading: Text("$daysDone Days"), - center: Text("${(percent * 100).floor()} %"), - trailing: Text("$daysLeft Days"), - progressColor: Colors.green, - backgroundColor: Colors.red, - lineHeight: 25, - ), - ), - Expanded( - flex: 1, - child: Text("$allDays Days"), - ), - ], - ), - ); - }), - ); - } -} - -class _ViewModel { - final TimeProgress timeProgress; - - _ViewModel({ - @required this.timeProgress, - }); - - static _ViewModel fromStore(Store store) { - return _ViewModel( - timeProgress: store.state.timeProgressList[0], - ); - } -} diff --git a/lib/selectors/time_progress_selectors.dart b/lib/selectors/time_progress_selectors.dart index 0ffa851..e9681c7 100644 --- a/lib/selectors/time_progress_selectors.dart +++ b/lib/selectors/time_progress_selectors.dart @@ -1,4 +1,8 @@ -import 'package:time_progress_calculator/models/app_state.dart'; -import 'package:time_progress_calculator/models/time_progress.dart'; +import 'package:time_progress_tracker/models/app_state.dart'; +import 'package:time_progress_tracker/models/time_progress.dart'; -List timeProgressListSelector(AppState state) => state.timeProgressList; +List timeProgressListSelector(AppState state) => + state.timeProgressList; + +TimeProgress timeProgressByIdSelector(AppState state, String id) => + state.timeProgressList.firstWhere((timeProgress) => timeProgress.id == id); diff --git a/lib/widgets/app_drawer_widget.dart b/lib/widgets/app_drawer_widget.dart new file mode 100644 index 0000000..faf04a5 --- /dev/null +++ b/lib/widgets/app_drawer_widget.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:percent_indicator/circular_percent_indicator.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_state.dart'; +import 'package:time_progress_tracker/models/time_progress.dart'; +import 'package:time_progress_tracker/screens/progress_dashboard_screen.dart'; +import 'package:time_progress_tracker/screens/progress_detail_screen.dart'; + +class AppDrawer extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Drawer( + child: StoreConnector( + converter: _ViewModel.fromStore, + onInit: loadTimeProgressListIfUnloaded, + builder: (context, _ViewModel vm) { + List drawerTileList = List(); + drawerTileList.add(DrawerHeader( + child: Text(TimeProgressTrackerApp.name), + decoration: BoxDecoration(color: Colors.blue), + margin: EdgeInsets.zero, + )); + drawerTileList.add(Container( + color: Colors.lightBlue, + margin: EdgeInsets.only(bottom: 8), + child: ListTile( + title: Text(ProgressDashboardScreen.title), + trailing: Icon(Icons.dashboard), + onTap: () { + Navigator.pop(context); + Navigator.pushNamed(context, ProgressDashboardScreen.routeName); + }, + ), + )); + if (vm.timeProgressList.length > 0) { + for (TimeProgress tp in vm.timeProgressList) { + drawerTileList.add(ListTile( + title: Text(tp.name), + trailing: CircularPercentIndicator( + percent: tp.percentDone(), + radius: 40, + progressColor: Colors.green, + backgroundColor: Colors.red, + center: FittedBox( + fit: BoxFit.scaleDown, + child: + Text((tp.percentDone() * 100).floor().toString() + "%"), + ), + ), + onTap: () { + Navigator.pop(context); + Navigator.pushNamed( + context, + ProgressDetailScreen.routeName, + arguments: ProgressDetailScreenArguments(tp.id), + ); + }, + )); + if (vm.timeProgressList.last != tp) { + drawerTileList.add(Divider( + color: Colors.black12, + )); + } + } + } else { + drawerTileList.add(ListTile( + title: Text("You don't have any tracked time progress."), + )); + } + drawerTileList.add(Divider( + color: Colors.black38, + )); + drawerTileList.add(Container( + margin: EdgeInsets.only(bottom: 8), + child: ListTile( + title: Text("About"), + onTap: () { + showAboutDialog( + context: context, + applicationName: TimeProgressTrackerApp.name, + applicationVersion: ' Version 0.0.1', + applicationLegalese: '\u00a9Andreas Fahrecker 2020' + ); + }, + ), + )); + return ListView( + children: drawerTileList, + ); + }, + ), + ); + } +} + +class _ViewModel { + final List timeProgressList; + + _ViewModel({@required this.timeProgressList}); + + static _ViewModel fromStore(Store store) { + return _ViewModel( + timeProgressList: store.state.timeProgressList, + ); + } +} diff --git a/lib/widgets/app_yes_no_dialog_widget.dart b/lib/widgets/app_yes_no_dialog_widget.dart new file mode 100644 index 0000000..29b6d50 --- /dev/null +++ b/lib/widgets/app_yes_no_dialog_widget.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +class AppYesNoDialog extends StatelessWidget { + final String titleText; + final String contentText; + final void Function() onYesPressed; + final void Function() onNoPressed; + + AppYesNoDialog({ + Key key, + @required this.titleText, + @required this.contentText, + @required this.onYesPressed, + @required this.onNoPressed, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(titleText), + content: Text(contentText), + actions: [ + FlatButton( + child: Text("Yes"), + onPressed: onYesPressed, + ), + FlatButton( + child: Text("No"), + onPressed: onNoPressed, + ) + ], + ); + } +} diff --git a/lib/widgets/progress_detail_widgets/progress_detail_circular_percent_widget.dart b/lib/widgets/progress_detail_widgets/progress_detail_circular_percent_widget.dart new file mode 100644 index 0000000..d2b37eb --- /dev/null +++ b/lib/widgets/progress_detail_widgets/progress_detail_circular_percent_widget.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:percent_indicator/circular_percent_indicator.dart'; + +class ProgressDetailCircularPercent extends StatelessWidget { + final double percentDone; + + ProgressDetailCircularPercent({Key key, @required this.percentDone}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return CircularPercentIndicator( + radius: 100, + lineWidth: 10, + percent: percentDone, + progressColor: Colors.green, + backgroundColor: Colors.red, + center: Text("${(percentDone * 100).floor()} %"), + ); + } +} diff --git a/lib/widgets/progress_detail_widgets/progress_detail_edit_dates_row_widget.dart b/lib/widgets/progress_detail_widgets/progress_detail_edit_dates_row_widget.dart new file mode 100644 index 0000000..4ee96ac --- /dev/null +++ b/lib/widgets/progress_detail_widgets/progress_detail_edit_dates_row_widget.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_select_date_btn_widget.dart'; + +class ProgressDetailEditDatesRow extends StatelessWidget { + final DateTime startTime; + final DateTime endTime; + final void Function(DateTime) onStartTimeChanged; + final void Function(DateTime) onEndTimeChanged; + + ProgressDetailEditDatesRow({ + Key key, + @required this.startTime, + @required this.endTime, + @required this.onStartTimeChanged, + @required this.onEndTimeChanged, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + flex: 5, + child: ProgressDetailSelectDateButton( + leadingString: "Start Date:", + selectedDate: startTime, + onDateSelected: onStartTimeChanged, + ), + ), + Spacer( + flex: 1, + ), + Expanded( + flex: 5, + child: ProgressDetailSelectDateButton( + leadingString: "End Date:", + selectedDate: endTime, + onDateSelected: onEndTimeChanged, + ), + ) + ], + ); + } +} diff --git a/lib/widgets/progress_detail_widgets/progress_detail_fab_editing_row_widget.dart b/lib/widgets/progress_detail_widgets/progress_detail_fab_editing_row_widget.dart new file mode 100644 index 0000000..f6d774c --- /dev/null +++ b/lib/widgets/progress_detail_widgets/progress_detail_fab_editing_row_widget.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +class ProgressDetailFabEditingRow extends StatelessWidget { + final void Function() onSave; + final void Function() onCancelEdit; + + ProgressDetailFabEditingRow({ + Key key, + @required this.onSave, + @required this.onCancelEdit, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: FloatingActionButton( + heroTag: "saveEditedTimeProgressBTN", + child: Icon(Icons.save), + backgroundColor: Colors.green, + onPressed: this.onSave, + ), + ), + Expanded( + child: FloatingActionButton( + heroTag: "cancelEditTimeProgressBTN", + child: Icon(Icons.cancel), + backgroundColor: Colors.red, + onPressed: this.onCancelEdit, + )) + ], + ); + } +} diff --git a/lib/widgets/progress_detail_widgets/progress_detail_fab_row_widget.dart b/lib/widgets/progress_detail_widgets/progress_detail_fab_row_widget.dart new file mode 100644 index 0000000..0d1c1b6 --- /dev/null +++ b/lib/widgets/progress_detail_widgets/progress_detail_fab_row_widget.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +class ProgressDetailFabRow extends StatelessWidget { + final void Function() onEdit; + final void Function() onDelete; + + ProgressDetailFabRow( + {Key key, @required this.onEdit, @required this.onDelete}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: FloatingActionButton( + heroTag: "editTimeProgressBTN", + child: Icon(Icons.edit), + onPressed: onEdit, + ), + ), + Expanded( + child: FloatingActionButton( + heroTag: "deleteTimeProgressBTN", + child: Icon(Icons.delete), + backgroundColor: Colors.red, + onPressed: onDelete, + ), + ) + ], + ); + } +} diff --git a/lib/widgets/progress_detail_widgets/progress_detail_linear_percent_widget.dart b/lib/widgets/progress_detail_widgets/progress_detail_linear_percent_widget.dart new file mode 100644 index 0000000..62e1519 --- /dev/null +++ b/lib/widgets/progress_detail_widgets/progress_detail_linear_percent_widget.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:percent_indicator/linear_percent_indicator.dart'; +import 'package:time_progress_tracker/models/time_progress.dart'; + +class ProgressDetailLinearPercent extends StatelessWidget { + final TimeProgress timeProgress; + + ProgressDetailLinearPercent({Key key, @required this.timeProgress}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return LinearPercentIndicator( + padding: EdgeInsets.symmetric(horizontal: 15), + percent: this.timeProgress.percentDone(), + leading: Text("${this.timeProgress.daysBehind()} Days"), + center: Text("${(this.timeProgress.percentDone() * 100).floor()} %"), + trailing: Text("${this.timeProgress.daysLeft()} Days"), + progressColor: Colors.green, + backgroundColor: Colors.red, + lineHeight: 25, + ); + } +} diff --git a/lib/widgets/progress_detail_widgets/progress_detail_select_date_btn_widget.dart b/lib/widgets/progress_detail_widgets/progress_detail_select_date_btn_widget.dart new file mode 100644 index 0000000..c4dc88d --- /dev/null +++ b/lib/widgets/progress_detail_widgets/progress_detail_select_date_btn_widget.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class ProgressDetailSelectDateButton extends StatelessWidget { + final String leadingString; + final DateTime selectedDate; + final void Function(DateTime) onDateSelected; + + ProgressDetailSelectDateButton({ + Key key, + @required this.leadingString, + @required this.selectedDate, + @required this.onDateSelected, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return FlatButton( + color: Colors.blue, + child: Text( + "$leadingString ${selectedDate.toLocal().toString().split(" ")[0]}"), + onPressed: () async { + DateTime picked = await showDatePicker( + context: context, + initialDate: selectedDate, + firstDate: DateTime(selectedDate.year - 5), + lastDate: DateTime(selectedDate.year + 5), + ); + onDateSelected(picked); + }, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 19277ea..2462b92 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,42 +21,42 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.2" + version: "2.5.0-nullsafety.1" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety.1" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.1.0-nullsafety.3" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.2.0-nullsafety.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.1.0-nullsafety.1" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.13" + version: "1.15.0-nullsafety.3" convert: dependency: transitive description: @@ -84,7 +84,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0-nullsafety.1" ffi: dependency: transitive description: @@ -117,7 +117,7 @@ packages: name: flutter_redux url: "https://pub.dartlang.org" source: hosted - version: "0.6.0" + version: "0.7.0" flutter_test: dependency: "direct dev" description: flutter @@ -148,21 +148,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.8" + version: "0.12.10-nullsafety.1" meta: dependency: "direct main" description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.3.0-nullsafety.3" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0-nullsafety.1" path_provider_linux: dependency: transitive description: @@ -176,28 +176,28 @@ packages: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.4" path_provider_windows: dependency: transitive description: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+1" + version: "0.0.4+3" percent_indicator: dependency: "direct main" description: name: percent_indicator url: "https://pub.dartlang.org" source: hosted - version: "2.1.7+4" + version: "2.1.8" petitparser: dependency: transitive description: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "3.0.4" + version: "3.1.0" platform: dependency: transitive description: @@ -232,21 +232,21 @@ packages: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "0.5.12" + version: "0.5.12+4" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.2+2" + version: "0.0.2+4" shared_preferences_macos: dependency: transitive description: name: shared_preferences_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+10" + version: "0.0.1+11" shared_preferences_platform_interface: dependency: transitive description: @@ -267,7 +267,7 @@ packages: name: shared_preferences_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+1" + version: "0.0.1+3" sky_engine: dependency: transitive description: flutter @@ -279,56 +279,56 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0-nullsafety.2" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.5" + version: "1.10.0-nullsafety.1" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0-nullsafety.1" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0-nullsafety.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0-nullsafety.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.17" + version: "0.2.19-nullsafety.2" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0-nullsafety.3" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.0-nullsafety.3" win32: dependency: transitive description: @@ -342,7 +342,7 @@ packages: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "0.1.0" + version: "0.1.2" xml: dependency: transitive description: @@ -358,5 +358,5 @@ packages: source: hosted version: "2.2.1" sdks: - dart: ">=2.9.0-14.0.dev <3.0.0" + dart: ">=2.10.0-110 <2.11.0" flutter: ">=1.12.13+hotfix.5 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 398019c..55e455d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,4 +1,4 @@ -name: time_progress_calculator +name: time_progress_tracker description: A Flutter Application to create Timers with a percentage indicator. # The following line prevents the package from being accidentally published to