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 <AndreasFahrecker@gmail.com>
This commit is contained in:
Andreas Fahrecker 2020-11-20 01:17:17 +01:00 committed by GitHub
parent 976fbec455
commit f013c0de65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 984 additions and 283 deletions

View File

@ -1,4 +1,4 @@
# time_progress_calculator # time_progress_tracker
A Flutter Application to create Timers with a percentage indicator. A Flutter Application to create Timers with a percentage indicator.

View File

@ -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 {} class LoadTimeProgressListAction {}
@ -28,3 +30,9 @@ class DeleteTimeProgressAction {
DeleteTimeProgressAction(this.id); DeleteTimeProgressAction(this.id);
} }
void loadTimeProgressListIfUnloaded(Store<AppState> store) {
if (!store.state.hasLoaded) {
store.dispatch(LoadTimeProgressListAction());
}
}

View File

@ -1,29 +1,36 @@
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:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:time_progress_calculator/models/app_state.dart'; import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_calculator/screens/progress_screen.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<AppState> store; final Store<AppState> store;
TimeProgressCalculatorApp({Key key, this.store}) : super(key: key); TimeProgressTrackerApp({Key key, this.store}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StoreProvider( return StoreProvider(
store: store, store: store,
child: MaterialApp( child: MaterialApp(
title: "Time Progress Calculator", title: name,
theme: ThemeData( theme: ThemeData(
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity), visualDensity: VisualDensity.adaptivePlatformDensity,
initialRoute: "/", ),
initialRoute: ProgressDashboardScreen.routeName,
routes: { routes: {
"/": (BuildContext context) => ProgressScreen( ProgressDashboardScreen.routeName: (BuildContext context) =>
name: "Zivildienst", ProgressDashboardScreen(),
context: context, ProgressDetailScreen.routeName: (BuildContext context) =>
) ProgressDetailScreen(),
ProgressCreationScreen.routeName: (BuildContext context) =>
ProgressCreationScreen(),
}, },
), ),
); );

View File

@ -1,16 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.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_calculator/app.dart'; import 'package:time_progress_tracker/app.dart';
import 'package:time_progress_calculator/middleware/store_time_progress_middleware.dart'; import 'package:time_progress_tracker/middleware/store_time_progress_middleware.dart';
import 'package:time_progress_calculator/models/app_state.dart'; import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_calculator/persistence/time_progress_repository.dart'; import 'package:time_progress_tracker/persistence/time_progress_repository.dart';
import 'package:time_progress_calculator/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();
runApp(TimeProgressCalculatorApp( runApp(TimeProgressTrackerApp(
store: Store<AppState>( store: Store<AppState>(
appStateReducer, appStateReducer,
initialState: AppState.initial(), initialState: AppState.initial(),

View File

@ -1,10 +1,10 @@
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:time_progress_calculator/actions/actions.dart'; import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_calculator/models/app_state.dart'; import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_calculator/models/time_progress.dart'; import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_calculator/persistence/time_progress_entity.dart'; import 'package:time_progress_tracker/persistence/time_progress_entity.dart';
import 'package:time_progress_calculator/persistence/time_progress_repository.dart'; import 'package:time_progress_tracker/persistence/time_progress_repository.dart';
import 'package:time_progress_calculator/selectors/time_progress_selectors.dart'; import 'package:time_progress_tracker/selectors/time_progress_selectors.dart';
List<Middleware<AppState>> createStoreTimeProgressListMiddleware( List<Middleware<AppState>> createStoreTimeProgressListMiddleware(
TimeProgressRepository repository) { TimeProgressRepository repository) {
@ -19,7 +19,8 @@ List<Middleware<AppState>> createStoreTimeProgressListMiddleware(
]; ];
} }
Middleware<AppState> _createSaveTimeProgressList(TimeProgressRepository repository) { Middleware<AppState> _createSaveTimeProgressList(
TimeProgressRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) { return (Store<AppState> store, dynamic action, NextDispatcher next) {
next(action); next(action);
@ -31,12 +32,18 @@ Middleware<AppState> _createSaveTimeProgressList(TimeProgressRepository reposito
}; };
} }
Middleware<AppState> _createLoadTimeProgressList(TimeProgressRepository repository) { Middleware<AppState> _createLoadTimeProgressList(
TimeProgressRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) { return (Store<AppState> store, dynamic action, NextDispatcher next) {
repository.loadTimeProgressList().then((timeProgresses) { repository.loadTimeProgressList().then((timeProgresses) {
store.dispatch( List<TimeProgress> timeProgressList =
TimeProgressListLoadedAction(timeProgresses.map<TimeProgress>(TimeProgress.fromEntity).toList()), timeProgresses.map<TimeProgress>(TimeProgress.fromEntity).toList();
); if (timeProgressList == null) {
timeProgressList = [];
}
store.dispatch(TimeProgressListLoadedAction(
timeProgressList,
));
}).catchError((_) => store.dispatch(TimeProgressListNotLoadedAction())); }).catchError((_) => store.dispatch(TimeProgressListNotLoadedAction()));
}; };
} }

View File

@ -1,35 +1,36 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:time_progress_calculator/models/time_progress.dart'; import 'package:time_progress_tracker/models/time_progress.dart';
@immutable @immutable
class AppState { class AppState {
final bool isLoading; final bool hasLoaded;
final List<TimeProgress> timeProgressList; final List<TimeProgress> timeProgressList;
AppState({ AppState({
this.isLoading = false, this.hasLoaded = false,
this.timeProgressList = const [], this.timeProgressList = const [],
}); });
factory AppState.initial() => AppState(isLoading: true); factory AppState.initial() => AppState(hasLoaded: false);
AppState copyWith({ AppState copyWith({
bool isLoading, bool hasLoaded,
List<TimeProgress> timeProgressList, List<TimeProgress> timeProgressList,
}) { }) {
return AppState( return AppState(
isLoading: isLoading ?? this.isLoading, hasLoaded: hasLoaded ?? this.hasLoaded,
timeProgressList: timeProgressList ?? this.timeProgressList, timeProgressList: timeProgressList ?? this.timeProgressList,
); );
} }
@override @override
int get hashCode => timeProgressList.hashCode; int get hashCode => hasLoaded.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 &&
timeProgressList == other.timeProgressList; timeProgressList == other.timeProgressList;
} }

View File

@ -1,26 +1,51 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:time_progress_calculator/persistence/time_progress_entity.dart'; import 'package:time_progress_tracker/persistence/time_progress_entity.dart';
import 'package:time_progress_calculator/uuid.dart'; import 'package:time_progress_tracker/uuid.dart';
@immutable @immutable
class TimeProgress { class TimeProgress {
final String id; final String id;
final String name;
final DateTime startTime; final DateTime startTime;
final DateTime endTime; final DateTime endTime;
TimeProgress(this.startTime, this.endTime, {String id}) TimeProgress(this.name, this.startTime, this.endTime, {String id})
: id = id ?? Uuid().generateV4(); : 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( return TimeProgress(
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() {
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 @override
int get hashCode => id.hashCode ^ startTime.hashCode ^ endTime.hashCode; int get hashCode =>
id.hashCode ^ name.hashCode ^ startTime.hashCode ^ endTime.hashCode;
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@ -28,20 +53,22 @@ class TimeProgress {
other is TimeProgress && other is TimeProgress &&
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
id == other.id && id == other.id &&
name == other.name &&
startTime == other.startTime && startTime == other.startTime &&
endTime == other.endTime; endTime == other.endTime;
@override @override
String toString() { String toString() {
return "Timer{id: $id, startTimer: $startTime, endTimer: $endTime}"; return "TimeProgress{id: $id, name: $name, startTime: $startTime, endTime: $endTime}";
} }
TimeProgressEntity toEntity() { TimeProgressEntity toEntity() {
return TimeProgressEntity(id, startTime, endTime); return TimeProgressEntity(id, name, startTime, endTime);
} }
static TimeProgress fromEntity(TimeProgressEntity entity) { static TimeProgress fromEntity(TimeProgressEntity entity) {
return TimeProgress( return TimeProgress(
entity.name,
entity.startTime, entity.startTime,
entity.endTime, entity.endTime,
id: entity.id ?? Uuid().generateV4(), id: entity.id ?? Uuid().generateV4(),

View File

@ -1,12 +1,14 @@
class TimeProgressEntity { class TimeProgressEntity {
final String id; final String id;
final String name;
final DateTime startTime; final DateTime startTime;
final DateTime endTime; final DateTime endTime;
TimeProgressEntity(this.id, this.startTime, this.endTime); TimeProgressEntity(this.id, this.name, this.startTime, this.endTime);
@override @override
int get hashCode => id.hashCode ^ startTime.hashCode ^ endTime.hashCode; int get hashCode =>
id.hashCode ^ name.hashCode ^ startTime.hashCode ^ endTime.hashCode;
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
@ -14,23 +16,26 @@ class TimeProgressEntity {
other is TimeProgressEntity && other is TimeProgressEntity &&
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
id == other.id && id == other.id &&
name == other.name &&
startTime == other.startTime && startTime == other.startTime &&
endTime == other.endTime; endTime == other.endTime;
Map<String, Object> toJson() { Map<String, Object> toJson() {
return { return {
"id": id, "id": id,
"name": name,
"startTime": startTime.millisecondsSinceEpoch, "startTime": startTime.millisecondsSinceEpoch,
"endTime": startTime.millisecondsSinceEpoch "endTime": endTime.millisecondsSinceEpoch
}; };
} }
static TimeProgressEntity fromJson(Map<String, Object> json) { static TimeProgressEntity fromJson(Map<String, Object> json) {
final String id = json["id"] as String; final String id = json["id"] as String;
final String name = json["name"] as String;
final DateTime startTime = final DateTime startTime =
DateTime.fromMillisecondsSinceEpoch(json["startTime"] as int); DateTime.fromMillisecondsSinceEpoch(json["startTime"] as int);
final DateTime endTime = final DateTime endTime =
DateTime.fromMillisecondsSinceEpoch(json["endTime"] as int); DateTime.fromMillisecondsSinceEpoch(json["endTime"] as int);
return TimeProgressEntity(id, startTime, endTime); return TimeProgressEntity(id, name, startTime, endTime);
} }
} }

View File

@ -1,7 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart'; 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 { class TimeProgressRepository {
static const String _key = "time_progress_repo"; static const String _key = "time_progress_repo";
@ -12,16 +13,19 @@ class TimeProgressRepository {
Future<List<TimeProgressEntity>> loadTimeProgressList() { Future<List<TimeProgressEntity>> loadTimeProgressList() {
final String jsonString = this.prefs.getString(_key); final String jsonString = this.prefs.getString(_key);
return codec if (jsonString == null) {
return Future<List<TimeProgressEntity>>.value([]);
}
return Future<List<TimeProgressEntity>>.value(codec
.decode(jsonString)["timers"] .decode(jsonString)["timers"]
.cast<Map<String, Object>>() .cast<Map<String, Object>>()
.map<TimeProgressEntity>(TimeProgressEntity.fromJson) .map<TimeProgressEntity>(TimeProgressEntity.fromJson)
.toList(growable: false); .toList(growable: false));
} }
Future<bool> saveTimeProgressList(List<TimeProgressEntity> timeProgressList) { Future<bool> saveTimeProgressList(List<TimeProgressEntity> timeProgressList) {
final String jsonString = codec final String jsonString = codec.encode(
.encode({"timers": timeProgressList.map((timer) => timer.toJson()).toList()}); {"timers": timeProgressList.map((timer) => timer.toJson()).toList()});
return this.prefs.setString(_key, jsonString); return this.prefs.setString(_key, jsonString);
} }
} }

View File

@ -1,6 +1,10 @@
import 'package:time_progress_calculator/models/app_state.dart'; import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_calculator/reducers/time_progress_list_reducer.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) { 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),
);
} }

View File

@ -0,0 +1,15 @@
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/actions/actions.dart';
final hasLoadedReducer = combineReducers<bool>([
TypedReducer<bool, TimeProgressListLoadedAction>(_setLoaded),
TypedReducer<bool, TimeProgressListNotLoadedAction>(_setUnloaded)
]);
bool _setLoaded(bool hasLoaded, TimeProgressListLoadedAction action) {
return true;
}
bool _setUnloaded(bool hasLoaded, TimeProgressListNotLoadedAction action) {
return false;
}

View File

@ -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:redux/redux.dart';
import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
final timeProgressListReducer = combineReducers<List<TimeProgress>>([ final timeProgressListReducer = combineReducers<List<TimeProgress>>([
TypedReducer<List<TimeProgress>, TimeProgressListLoadedAction>( TypedReducer<List<TimeProgress>, TimeProgressListLoadedAction>(

View File

@ -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<StatefulWidget> createState() {
return _ProgressCreationScreenState();
}
}
class _ProgressCreationScreenState extends State<ProgressCreationScreen> {
final TextEditingController _nameController = TextEditingController();
DateTime pickedStartTime = DateTime.now();
DateTime pickedEndTime = DateTime(
DateTime.now().year + 1, DateTime.now().month, DateTime.now().day);
Future<DateTime> _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<AppState>(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: <Widget>[
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: <Widget>[
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: <Widget>[
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);
},
),
)
],
),
);
}
}

View File

@ -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<Widget> dashboardTileList = List<Widget>();
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<TimeProgress> timeProgressList;
final bool hasLoaded;
_ViewModel({
@required this.timeProgressList,
@required this.hasLoaded,
});
static _ViewModel fromStore(Store<AppState> store) {
return _ViewModel(
timeProgressList: store.state.timeProgressList,
hasLoaded: store.state.hasLoaded,
);
}
}

View File

@ -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<StatefulWidget> createState() {
return _ProgressDetailScreenState();
}
}
class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
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<AppState> 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<AppState> store, id) {
setState(() {
_isBeingEdited = true;
_editedProgress = timeProgressByIdSelector(store.state, id);
_nameController.text = _editedProgress.name;
});
}
void _showDeleteTimeProgressDialog(Store<AppState> 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<AppState> 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<AppState> store = StoreProvider.of<AppState>(context);
return Scaffold(
appBar: AppBar(
title: Text("Progress"),
),
drawer: AppDrawer(),
body: Container(
margin: EdgeInsets.all(8),
child: StoreConnector(
converter: (Store<AppState> store) =>
_ViewModel.fromStoreAndArg(store, args),
onInit: loadTimeProgressListIfUnloaded,
builder: (BuildContext context, _ViewModel vm) {
return Column(
children: <Widget>[
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<AppState> store, ProgressDetailScreenArguments args) {
return _ViewModel(
timeProgress: timeProgressByIdSelector(store.state, args.id),
);
}
}

View File

@ -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<StatefulWidget> createState() {
return _ProgressScreenState();
}
}
class _ProgressScreenState extends State<ProgressScreen> {
void _selectStartDate(BuildContext context) async {
Store<AppState> store = StoreProvider.of<AppState>(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<AppState> store = StoreProvider.of<AppState>(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<AppState>(widget.context).state.timeProgressList.length < 1) {
StoreProvider.of<AppState>(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: <Widget>[
Expanded(
flex: 1,
child: Row(
children: <Widget>[
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: <Widget>[
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<AppState> store) {
return _ViewModel(
timeProgress: store.state.timeProgressList[0],
);
}
}

View File

@ -1,4 +1,8 @@
import 'package:time_progress_calculator/models/app_state.dart'; import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_calculator/models/time_progress.dart'; import 'package:time_progress_tracker/models/time_progress.dart';
List<TimeProgress> timeProgressListSelector(AppState state) => state.timeProgressList; List<TimeProgress> timeProgressListSelector(AppState state) =>
state.timeProgressList;
TimeProgress timeProgressByIdSelector(AppState state, String id) =>
state.timeProgressList.firstWhere((timeProgress) => timeProgress.id == id);

View File

@ -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<Widget> drawerTileList = List<Widget>();
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<TimeProgress> timeProgressList;
_ViewModel({@required this.timeProgressList});
static _ViewModel fromStore(Store<AppState> store) {
return _ViewModel(
timeProgressList: store.state.timeProgressList,
);
}
}

View File

@ -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: <Widget>[
FlatButton(
child: Text("Yes"),
onPressed: onYesPressed,
),
FlatButton(
child: Text("No"),
onPressed: onNoPressed,
)
],
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,42 +21,42 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.4.2" version: "2.5.0-nullsafety.1"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
name: boolean_selector name: boolean_selector
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0" version: "2.1.0-nullsafety.1"
characters: characters:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.1.0-nullsafety.3"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
name: charcode name: charcode
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.3" version: "1.2.0-nullsafety.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "1.1.0-nullsafety.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.14.13" version: "1.15.0-nullsafety.3"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -84,7 +84,7 @@ packages:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.2.0-nullsafety.1"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
@ -117,7 +117,7 @@ packages:
name: flutter_redux name: flutter_redux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.0" version: "0.7.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -148,21 +148,21 @@ packages:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.8" version: "0.12.10-nullsafety.1"
meta: meta:
dependency: "direct main" dependency: "direct main"
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.8" version: "1.3.0-nullsafety.3"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.0" version: "1.8.0-nullsafety.1"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@ -176,28 +176,28 @@ packages:
name: path_provider_platform_interface name: path_provider_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "1.0.4"
path_provider_windows: path_provider_windows:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.4+1" version: "0.0.4+3"
percent_indicator: percent_indicator:
dependency: "direct main" dependency: "direct main"
description: description:
name: percent_indicator name: percent_indicator
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.7+4" version: "2.1.8"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
name: petitparser name: petitparser
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.4" version: "3.1.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -232,21 +232,21 @@ packages:
name: shared_preferences name: shared_preferences
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.12" version: "0.5.12+4"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_linux name: shared_preferences_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.2+2" version: "0.0.2+4"
shared_preferences_macos: shared_preferences_macos:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_macos name: shared_preferences_macos
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.1+10" version: "0.0.1+11"
shared_preferences_platform_interface: shared_preferences_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -267,7 +267,7 @@ packages:
name: shared_preferences_windows name: shared_preferences_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.1+1" version: "0.0.1+3"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -279,56 +279,56 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.0" version: "1.8.0-nullsafety.2"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.5" version: "1.10.0-nullsafety.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0" version: "2.1.0-nullsafety.1"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.5" version: "1.1.0-nullsafety.1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.2.0-nullsafety.1"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.17" version: "0.2.19-nullsafety.2"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.3.0-nullsafety.3"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.8" version: "2.1.0-nullsafety.3"
win32: win32:
dependency: transitive dependency: transitive
description: description:
@ -342,7 +342,7 @@ packages:
name: xdg_directories name: xdg_directories
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.0" version: "0.1.2"
xml: xml:
dependency: transitive dependency: transitive
description: description:
@ -358,5 +358,5 @@ packages:
source: hosted source: hosted
version: "2.2.1" version: "2.2.1"
sdks: 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" flutter: ">=1.12.13+hotfix.5 <2.0.0"

View File

@ -1,4 +1,4 @@
name: time_progress_calculator name: time_progress_tracker
description: A Flutter Application to create Timers with a percentage indicator. description: A Flutter Application to create Timers with a percentage indicator.
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to