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:
parent
976fbec455
commit
f013c0de65
@ -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.
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
31
lib/app.dart
31
lib/app.dart
@ -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(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -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(),
|
||||||
|
@ -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()));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
15
lib/reducers/has_loaded_reducer.dart
Normal file
15
lib/reducers/has_loaded_reducer.dart
Normal 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;
|
||||||
|
}
|
@ -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>(
|
||||||
|
143
lib/screens/progress_creation_screen.dart
Normal file
143
lib/screens/progress_creation_screen.dart
Normal 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);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
94
lib/screens/progress_dashboard_screen.dart
Normal file
94
lib/screens/progress_dashboard_screen.dart
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
239
lib/screens/progress_detail_screen.dart
Normal file
239
lib/screens/progress_detail_screen.dart
Normal 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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
||||||
|
110
lib/widgets/app_drawer_widget.dart
Normal file
110
lib/widgets/app_drawer_widget.dart
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
34
lib/widgets/app_yes_no_dialog_widget.dart
Normal file
34
lib/widgets/app_yes_no_dialog_widget.dart
Normal 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,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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()} %"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
58
pubspec.lock
58
pubspec.lock
@ -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"
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user