18 Commits

Author SHA1 Message Date
c23863903a Updated Icon for iOS
Singed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-22 00:44:11 +01:00
65939fba30 Updated gitignore for ios
Singed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-22 00:42:55 +01:00
f592e7b66b Fixed Progress Editor Bug
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-13 00:54:16 +01:00
9ee29a7c7c Code cleanup
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-12 00:34:14 +01:00
d06d7f1448 Code cleanup
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-12 00:17:19 +01:00
5c2592f601 Cleanup of Progress Detail Screen
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-11 23:38:02 +01:00
c09b164a61 Capped Percent Done Value between 0 and 1.
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-11 20:28:40 +01:00
1826bbe421 Created ProgressViewWidget and rewritten ProgressDetailScreen with it
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-11 20:25:24 +01:00
1abfd0c1f5 Created ProgressEditorWidget and rewritten ProgressCreationScreen with it
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-11 18:35:47 +01:00
763cf9d627 Created DatePickerBtn and Started Using it in ProgressCreationScreen
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-11 17:05:23 +01:00
6dde08d74d Fixed Delete Time Progress Bug
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-11 15:30:48 +01:00
fad8d8b49b Fixed Navigator Bug in new App Layout
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-11 15:06:25 +01:00
d289cfc4d5 Renamed ProgressDashboardScreen to HomeScreen
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-11 15:01:53 +01:00
409ccbcdda New App Layout
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-10 19:36:04 +01:00
c5e240e813 Started Using ThemeData, Changed Primary Color to indigo and corrected logo to indigo.
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-10 15:04:51 +01:00
44db690a4a Replaced Logo, forgot to run flutter_launcher_icons:main
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-09 17:33:39 +01:00
8156b3f529 Replaced Logo
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-09 17:19:49 +01:00
58bc713227 Feature/bugfix 02 past time progresses (#5)
* Replaced startedTimeProgressSelectors with currentTimeProgressSelector.
* Added pastTimeProgressSelector.
* Fixed ProgressDetailScreen for PastProgresses.
* Fixed ProgressDashBoard for PastProgresses.
* Fixed AppDrawer for PastProgresses.
* Increased Version Number

Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-01-28 23:14:45 +01:00
56 changed files with 921 additions and 927 deletions

97
.gitignore vendored
View File

@ -1,5 +1,6 @@
# Miscellaneous # Miscellaneous
*.class *.class
*.lock
*.log *.log
*.pyc *.pyc
*.swp *.swp
@ -15,30 +16,102 @@
*.iws *.iws
.idea/ .idea/
# The .vscode folder contains launch configuration and tasks you configure in # Visual Studio Code related
# VS Code which you may wish to be included in version control, so this line .classpath
# is commented out by default. .project
#.vscode/ .settings/
.vscode/
# Flutter repo-specific
/bin/cache/
/bin/internal/bootstrap.bat
/bin/internal/bootstrap.sh
/bin/mingit/
/dev/benchmarks/mega_gallery/
/dev/bots/.recipe_deps
/dev/bots/android_tools/
/dev/devicelab/ABresults*.json
/dev/docs/doc/
/dev/docs/flutter.docs.zip
/dev/docs/lib/
/dev/docs/pubspec.yaml
/dev/integration_tests/**/xcuserdata
/dev/integration_tests/**/Pods
/packages/flutter/coverage/
version
analysis_benchmark.json
# packages file containing multi-root paths
.packages.generated
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
**/doc/api/ **/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/ .dart_tool/
.flutter-plugins .flutter-plugins
.flutter-plugins-dependencies .flutter-plugins-dependencies
**/generated_plugin_registrant.dart
.packages .packages
.pub-cache/ .pub-cache/
.pub/ .pub/
/build/ build/
flutter_*.png
linked_*.ds
unlinked.ds
unlinked_spec.ds
# Web related # Android related
lib/generated_plugin_registrant.dart **/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
**/android/key.properties
*.jks
# Symbolication related # iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/.last_build_id
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# macOS
**/macos/Flutter/GeneratedPluginRegistrant.swift
# Coverage
coverage/
# Symbols
app.*.symbols app.*.symbols
# Obfuscation related
app.*.map.json
# Exceptions to above rules. # Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -1,4 +1,4 @@
flutter_icons: flutter_icons:
android: true android: true
ios: false ios: true
image_path: "assets/icons/launcher_icon.png" image_path: "assets/icons/launcher_icon.png"

View File

@ -1 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig" #include "Generated.xcconfig"

View File

@ -1 +1,2 @@
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig" #include "Generated.xcconfig"

View File

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
4D431DF94830EB4E4109ECB2 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A0182DC80C66D064A11FDF0B /* libPods-Runner.a */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
@ -32,6 +33,7 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
37CBB71D7FE5A5197D6A6BF7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
@ -44,6 +46,9 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A0182DC80C66D064A11FDF0B /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
F9B8D838B24E4D784CD9D717 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
FF252FCCD702699EBF6FC287 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -51,12 +56,21 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
4D431DF94830EB4E4109ECB2 /* libPods-Runner.a in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
79DE0847F7F4B7CF08AEE47D /* Frameworks */ = {
isa = PBXGroup;
children = (
A0182DC80C66D064A11FDF0B /* libPods-Runner.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = { 9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -74,7 +88,8 @@
9740EEB11CF90186004384FC /* Flutter */, 9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, 9DBFAAD22EAE0F654D097AD5 /* Pods */,
79DE0847F7F4B7CF08AEE47D /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -110,6 +125,17 @@
name = "Supporting Files"; name = "Supporting Files";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
9DBFAAD22EAE0F654D097AD5 /* Pods */ = {
isa = PBXGroup;
children = (
37CBB71D7FE5A5197D6A6BF7 /* Pods-Runner.debug.xcconfig */,
FF252FCCD702699EBF6FC287 /* Pods-Runner.release.xcconfig */,
F9B8D838B24E4D784CD9D717 /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -117,6 +143,7 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
B3E7CCA0AF60658FE8F4BF7D /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */, 9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */, 97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */, 97C146EB1CF9000F007C117D /* Frameworks */,
@ -208,6 +235,28 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
}; };
B3E7CCA0AF60658FE8F4BF7D /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */

View File

@ -2,6 +2,6 @@
<Workspace <Workspace
version = "1.0"> version = "1.0">
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "self:">
</FileRef> </FileRef>
</Workspace> </Workspace>

View File

@ -4,4 +4,7 @@
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "group:Runner.xcodeproj">
</FileRef> </FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace> </Workspace>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -3,19 +3,17 @@ import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:time_progress_tracker/models/app_state.dart'; import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_tracker/screens/progress_creation_screen.dart'; import 'package:time_progress_tracker/screens/progress_creation_screen.dart';
import 'package:time_progress_tracker/screens/progress_dashboard_screen.dart'; import 'package:time_progress_tracker/screens/home_screen.dart';
import 'package:time_progress_tracker/screens/progress_detail_screen.dart'; import 'package:time_progress_tracker/screens/progress_detail_screen.dart';
class TimeProgressTrackerApp extends StatelessWidget { class TimeProgressTrackerApp extends StatelessWidget {
static const String name = "Time Progress Tracker"; static const String name = "Time Progress Tracker";
final Store<AppState> store; final Store<AppState> store;
final String appVersion;
TimeProgressTrackerApp({ TimeProgressTrackerApp({
Key key, Key key,
this.store, this.store,
this.appVersion,
}) : super(key: key); }) : super(key: key);
@override @override
@ -25,23 +23,19 @@ class TimeProgressTrackerApp extends StatelessWidget {
child: MaterialApp( child: MaterialApp(
title: name, title: name,
theme: ThemeData( theme: ThemeData(
primarySwatch: Colors.blue, primarySwatch: Colors.indigo,
accentColor: Colors.indigoAccent,
brightness: Brightness.light,
visualDensity: VisualDensity.adaptivePlatformDensity, visualDensity: VisualDensity.adaptivePlatformDensity,
), ),
initialRoute: ProgressDashboardScreen.routeName, initialRoute: HomeScreen.routeName,
routes: { routes: {
ProgressDashboardScreen.routeName: (BuildContext context) => HomeScreen.routeName: (BuildContext context) =>
ProgressDashboardScreen( HomeScreen(),
appVersion: appVersion,
),
ProgressDetailScreen.routeName: (BuildContext context) => ProgressDetailScreen.routeName: (BuildContext context) =>
ProgressDetailScreen( ProgressDetailScreen(),
appVersion: appVersion,
),
ProgressCreationScreen.routeName: (BuildContext context) => ProgressCreationScreen.routeName: (BuildContext context) =>
ProgressCreationScreen( ProgressCreationScreen(),
appVersion: appVersion,
),
}, },
), ),
); );

View File

@ -19,6 +19,5 @@ Future<void> main() async {
TimeProgressRepository(await SharedPreferences.getInstance()), TimeProgressRepository(await SharedPreferences.getInstance()),
), ),
), ),
appVersion: (await PackageInfo.fromPlatform()).version,
)); ));
} }

View File

@ -5,3 +5,14 @@ class TimeProgressInvalidNameException implements Exception {
String errMsg() => "The name of a TimeProgress can't be: $invalidName"; String errMsg() => "The name of a TimeProgress can't be: $invalidName";
} }
class TimeProgressStartTimeIsNotBeforeEndTimeException implements Exception {
final startTime;
final endTime;
TimeProgressStartTimeIsNotBeforeEndTimeException(
this.startTime, this.endTime);
String errMsg() =>
"The Start Time has to be before the end time. Therefore these values are invalid: Start Time: $startTime EndTime: $endTime";
}

View File

@ -11,15 +11,12 @@ class TimeProgress {
final DateTime endTime; final DateTime endTime;
TimeProgress(this.name, this.startTime, this.endTime, {String id}) TimeProgress(this.name, this.startTime, this.endTime, {String id})
: id = id ?? Uuid().generateV4() { : id = id ?? Uuid().generateV4();
if (this.name == null || this.name == "") {
throw new TimeProgressInvalidNameException(this.name);
}
}
factory TimeProgress.initialDefault() { factory TimeProgress.initialDefault() {
int thisYear = DateTime.now().year; int thisYear = DateTime.now().year;
return TimeProgress("Initial Name", DateTime(thisYear - 1), DateTime(thisYear + 1)); return TimeProgress(
"Initial Name", DateTime(thisYear - 1), DateTime(thisYear + 1));
} }
TimeProgress copyWith( TimeProgress copyWith(
@ -45,7 +42,20 @@ class TimeProgress {
} }
double percentDone() { double percentDone() {
return this.daysBehind() / (this.allDays() / 100) / 100; double percent = this.daysBehind() / (this.allDays() / 100) / 100;
if (percent < 0) percent = 0;
if (percent > 1) percent = 1;
return percent;
}
bool hasStarted() {
return DateTime.now().millisecondsSinceEpoch >
startTime.millisecondsSinceEpoch;
}
bool hasEnded() {
return DateTime.now().millisecondsSinceEpoch >
endTime.millisecondsSinceEpoch;
} }
@override @override
@ -68,6 +78,11 @@ class TimeProgress {
} }
TimeProgressEntity toEntity() { TimeProgressEntity toEntity() {
if (!TimeProgress.isNameValid(name))
throw new TimeProgressInvalidNameException(name);
if (!TimeProgress.areTimesValid(startTime, endTime))
throw new TimeProgressStartTimeIsNotBeforeEndTimeException(
startTime, endTime);
return TimeProgressEntity(id, name, startTime, endTime); return TimeProgressEntity(id, name, startTime, endTime);
} }
@ -79,4 +94,17 @@ class TimeProgress {
id: entity.id ?? Uuid().generateV4(), id: entity.id ?? Uuid().generateV4(),
); );
} }
static bool isValid(TimeProgress tp) {
return TimeProgress.isNameValid(tp.name) &&
TimeProgress.areTimesValid(tp.startTime, tp.endTime);
}
static bool isNameValid(String name) {
return name != null && name != "" && name.length > 2 && name.length < 21;
}
static bool areTimesValid(DateTime startTime, DateTime endTime) {
return startTime.isBefore(endTime);
}
} }

View File

@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/screens/progress_creation_screen.dart';
import 'package:time_progress_tracker/widgets/home/home_bottom_navbar.dart';
import 'package:time_progress_tracker/widgets/home/tabs/home_active_progresses_tab.dart';
import 'package:time_progress_tracker/widgets/home/tabs/home_inactive_progresses_tab.dart';
import 'package:time_progress_tracker/widgets/home/tabs/home_settings_tab.dart';
class HomeScreen extends StatefulWidget {
static const routeName = "/home";
static const title = "Time Progress Tracker";
@override
State<StatefulWidget> createState() {
return _HomeScreenState();
}
}
class _HomeScreenState extends State<HomeScreen> {
int _currentIndex = 0;
final List<Widget> _children = [
HomeActiveProgressesTab(),
HomeInactiveProgressesTab(),
HomeSettingsTab(),
];
void onBottomTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(HomeScreen.title),
),
body: _children[_currentIndex],
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: _currentIndex != 2
? FloatingActionButton(
heroTag: "createProgressBTN",
child: Icon(Icons.add),
onPressed: () {
Navigator.pushNamed(context, ProgressCreationScreen.routeName);
},
)
: null,
bottomNavigationBar: HomeBottomNavBar(
currentIndex: _currentIndex,
onTap: onBottomTabTapped,
),
);
}
}

View File

@ -2,19 +2,13 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:time_progress_tracker/actions/actions.dart'; import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/app_exceptions.dart';
import 'package:time_progress_tracker/models/app_state.dart'; import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_tracker/models/time_progress.dart'; import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/screens/progress_dashboard_screen.dart'; import 'package:time_progress_tracker/widgets/progress_editor_widget.dart';
import 'package:time_progress_tracker/widgets/app_drawer_widget.dart';
class ProgressCreationScreen extends StatefulWidget { class ProgressCreationScreen extends StatefulWidget {
static const routeName = "/progress-creation"; static const routeName = "/create-progress";
static const title = "Create Time Progress";
final String appVersion;
ProgressCreationScreen({Key key, @required this.appVersion})
: super(key: key);
@override @override
State<StatefulWidget> createState() { State<StatefulWidget> createState() {
@ -23,118 +17,29 @@ class ProgressCreationScreen extends StatefulWidget {
} }
class _ProgressCreationScreenState extends State<ProgressCreationScreen> { class _ProgressCreationScreenState extends State<ProgressCreationScreen> {
final TextEditingController _nameController = TextEditingController(); TimeProgress timeProgressToCreate =
DateTime pickedStartTime = DateTime.now(); TimeProgress("", DateTime.now(), DateTime(DateTime.now().year + 1));
DateTime pickedEndTime = DateTime( bool _isProgressValid = false;
DateTime.now().year + 1, DateTime.now().month, DateTime.now().day);
bool _validName = true; void onTimeProgressChanged(
TimeProgress newTimeProgress, bool isNewProgressValid) {
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) {
try {
TimeProgress tpToCreate =
TimeProgress(_nameController.text, pickedStartTime, pickedEndTime);
StoreProvider.of<AppState>(context)
.dispatch(AddTimeProgressAction(tpToCreate));
Navigator.pushNamed(context, ProgressDashboardScreen.routeName);
} on TimeProgressInvalidNameException catch (e) {
setState(() { setState(() {
_validName = false; timeProgressToCreate = newTimeProgress;
_isProgressValid = isNewProgressValid;
}); });
} }
}
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("Create Time Progress"), title: Text(ProgressCreationScreen.title),
),
drawer: AppDrawer(
appVersion: widget.appVersion,
), ),
body: Container( body: Container(
padding: EdgeInsets.all(8), padding: EdgeInsets.all(12),
child: Column( child: ProgressEditorWidget(
children: <Widget>[ timeProgress: timeProgressToCreate,
Expanded( onTimeProgressChanged: onTimeProgressChanged,
flex: 1,
child: TextField(
controller: _nameController,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Progress Name",
errorText: _validName
? null
: "The Name of the Time Progress has to be set.",
),
),
),
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, floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
@ -144,9 +49,13 @@ class _ProgressCreationScreenState extends State<ProgressCreationScreen> {
child: FloatingActionButton( child: FloatingActionButton(
heroTag: "createTimeProgressBTN", heroTag: "createTimeProgressBTN",
child: Icon(Icons.save), child: Icon(Icons.save),
onPressed: () { onPressed: _isProgressValid
_createTimeProgress(context); ? () {
}, StoreProvider.of<AppState>(context).dispatch(
AddTimeProgressAction(timeProgressToCreate));
Navigator.pop(context);
}
: null,
), ),
), ),
Expanded( Expanded(
@ -154,7 +63,7 @@ class _ProgressCreationScreenState extends State<ProgressCreationScreen> {
heroTag: "cancelTimeProgressCreationBTN", heroTag: "cancelTimeProgressCreationBTN",
child: Icon(Icons.cancel), child: Icon(Icons.cancel),
onPressed: () { onPressed: () {
Navigator.pushNamed(context, ProgressDashboardScreen.routeName); Navigator.pop(context);
}, },
), ),
) )

View File

@ -1,181 +0,0 @@
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/selectors/time_progress_selectors.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";
final String appVersion;
ProgressDashboardScreen({
Key key,
@required this.appVersion,
}) : super(key: key);
@override
Widget build(BuildContext context) {
AppBar appBar = AppBar(
title: Text(title),
);
return Scaffold(
appBar: appBar,
drawer: AppDrawer(
appVersion: appVersion,
),
body: StoreConnector(
converter: _ViewModel.fromStore,
onInit: loadTimeProgressListIfUnloaded,
builder: (BuildContext context, _ViewModel vm) {
if (!vm.hasLoaded) {
return Center(
child: CircularProgressIndicator(),
);
}
List<Widget> startedProgressesTileList = List<Widget>();
if (vm.hasStartedProgresses) {
for (TimeProgress tp in vm.startedTimeProgreses) {
startedProgressesTileList.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));
},
),
),
);
}
}
List<Widget> futureProgressesTileList = List<Widget>();
if (vm.hasFutureProgresses) {
for (TimeProgress tp in vm.futureTimeProgresses) {
futureProgressesTileList.add(
Card(
child: ListTile(
title: Text(tp.name),
subtitle: Text(
"Starts in ${tp.startTime.difference(DateTime.now()).inDays} Days."),
onTap: () {
Navigator.pushNamed(
context, ProgressDetailScreen.routeName,
arguments: ProgressDetailScreenArguments(tp.id));
},
),
),
);
}
}
double dividerHeight = 1;
double screenHeight = MediaQuery.of(context).size.height -
appBar.preferredSize.height -
24 -
dividerHeight; //Divider
List<Widget> columnChildren = List<Widget>();
int tpCount =
vm.startedTimeProgreses.length + vm.futureTimeProgresses.length;
if (vm.hasStartedProgresses) {
columnChildren.add(Container(
height: vm.hasFutureProgresses
? (screenHeight / tpCount) * vm.startedTimeProgreses.length
: screenHeight,
child: ListView(
padding: EdgeInsets.all(8),
children: startedProgressesTileList,
),
));
}
if (vm.hasStartedProgresses && vm.hasFutureProgresses) {
columnChildren.add(Divider(
height: dividerHeight,
));
}
if (vm.hasFutureProgresses) {
columnChildren.add(Container(
height: vm.hasStartedProgresses
? (screenHeight / tpCount) * vm.futureTimeProgresses.length
: screenHeight,
child: ListView(
padding: EdgeInsets.all(8),
children: futureProgressesTileList,
),
));
}
if (!vm.hasStartedProgresses && !vm.hasFutureProgresses) {
columnChildren.add(Container(
margin: EdgeInsets.all(16),
child: Center(
child: Text("You don't have any tracked Progress."),
),
));
}
return Column(
children: columnChildren,
);
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: FloatingActionButton(
heroTag: "createProgressBTN",
child: Icon(Icons.add),
onPressed: () {
Navigator.pushNamed(context, ProgressCreationScreen.routeName);
},
),
);
}
}
class _ViewModel {
final List<TimeProgress> startedTimeProgreses;
final bool hasStartedProgresses;
final List<TimeProgress> futureTimeProgresses;
final bool hasFutureProgresses;
final bool hasLoaded;
_ViewModel({
@required this.startedTimeProgreses,
@required this.hasStartedProgresses,
@required this.futureTimeProgresses,
@required this.hasFutureProgresses,
@required this.hasLoaded,
});
static _ViewModel fromStore(Store<AppState> store) {
List<TimeProgress> startedTPList =
startedTimeProgressesSelector(store.state);
List<TimeProgress> furtureTPList =
futureTimeProgressesSelector(store.state);
return _ViewModel(
startedTimeProgreses: startedTPList,
hasStartedProgresses: startedTPList.length > 0,
futureTimeProgresses: furtureTPList,
hasFutureProgresses: furtureTPList.length > 0,
hasLoaded: store.state.hasLoaded,
);
}
}

View File

@ -2,18 +2,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:time_progress_tracker/actions/actions.dart'; import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/app_exceptions.dart';
import 'package:time_progress_tracker/models/app_state.dart'; import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_tracker/models/time_progress.dart'; import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/screens/progress_dashboard_screen.dart'; import 'package:time_progress_tracker/screens/home_screen.dart';
import 'package:time_progress_tracker/selectors/time_progress_selectors.dart'; import 'package:time_progress_tracker/selectors/time_progress_selectors.dart';
import 'package:time_progress_tracker/widgets/app_drawer_widget.dart'; import 'package:time_progress_tracker/widgets/detail_screen_floating_action_buttons.dart';
import 'package:time_progress_tracker/widgets/app_yes_no_dialog_widget.dart'; import 'package:time_progress_tracker/widgets/progress_editor_widget.dart';
import 'package:time_progress_tracker/widgets/progress_detail_widgets/progress_detail_circular_percent_widget.dart'; import 'package:time_progress_tracker/widgets/progress_view_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 { class ProgressDetailScreenArguments {
final String id; final String id;
@ -22,14 +17,8 @@ class ProgressDetailScreenArguments {
} }
class ProgressDetailScreen extends StatefulWidget { class ProgressDetailScreen extends StatefulWidget {
static const routeName = "/progress-detail"; static const routeName = "/progress";
static const title = "Progress View";
final String appVersion;
ProgressDetailScreen({
Key key,
@required this.appVersion,
}) : super(key: key);
@override @override
State<StatefulWidget> createState() { State<StatefulWidget> createState() {
@ -38,109 +27,27 @@ class ProgressDetailScreen extends StatefulWidget {
} }
class _ProgressDetailScreenState extends State<ProgressDetailScreen> { class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
bool _isBeingEdited = false; bool _editMode = false, _isEditedProgressValid = false;
final TextEditingController _nameController = TextEditingController(); TimeProgress _editedProgress, _originalProgress;
TimeProgress _editedProgress = TimeProgress.initialDefault(); void _onEditedProgressChanged(
TimeProgress newProgress, bool isNewProgressValid) {
bool _validName = true;
void _onStartDateChanged(DateTime picked) {
if (picked != null) {
setState(() { setState(() {
_editedProgress = _editedProgress.copyWith(startTime: picked); _editedProgress = newProgress;
}); _isEditedProgressValid = isNewProgressValid;
}
}
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) { void _switchEditMode(bool newMode) {
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(() { setState(() {
_isBeingEdited = false; _editMode = newMode;
});
}
}
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) { void _cancelEditMode() {
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(() {
try {
TimeProgress editedProgress =
_editedProgress.copyWith(name: _nameController.text);
setState(() { setState(() {
_editedProgress = editedProgress; _editMode = false;
_validName = true; _editedProgress = _originalProgress;
});
} on TimeProgressInvalidNameException catch (e) {
setState(() {
_validName = false;
});
}
}); });
} }
@ -148,148 +55,74 @@ class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ProgressDetailScreenArguments args = final ProgressDetailScreenArguments args =
ModalRoute.of(context).settings.arguments; ModalRoute.of(context).settings.arguments;
final Store<AppState> store = StoreProvider.of<AppState>(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("Progress"), title: Text(ProgressDetailScreen.title),
),
drawer: AppDrawer(
appVersion: widget.appVersion,
), ),
body: Container( body: Container(
margin: EdgeInsets.all(8), margin: EdgeInsets.all(8),
child: StoreConnector( child: StoreConnector(
converter: (Store<AppState> store) =>
_ViewModel.fromStoreAndArg(store, args),
onInit: loadTimeProgressListIfUnloaded, onInit: loadTimeProgressListIfUnloaded,
builder: (BuildContext context, _ViewModel vm) { converter: (store) => timeProgressByIdSelector(store.state, args.id),
builder: (BuildContext context, TimeProgress timeProgress) {
if (timeProgress == null) //+++++Time Progress Not Found Error+++++
return Center(
child: Text("Error Invalid Time Progress"),
);
if (_editedProgress == null) {
_editedProgress = timeProgress;
_originalProgress = timeProgress;
} // initialize _editedProgress
List<Widget> columnChildren = [
Expanded(
child: ProgressViewWidget(
timeProgress: _editMode ? _editedProgress : timeProgress),
)
];
if (_editMode)
columnChildren.add(Expanded(
child: ProgressEditorWidget(
timeProgress: _editedProgress,
onTimeProgressChanged: _onEditedProgressChanged,
),
));
return Column( return Column(
children: <Widget>[ children: columnChildren,
Expanded(
flex: 1,
child: _isBeingEdited
? TextField(
controller: _nameController,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Progress Name",
errorText: _validName
? null
: "The Name of the Time Progress has to be set.",
),
)
: vm.hasProgressStarted
? FittedBox(
fit: BoxFit.fitWidth,
child: Text(
vm.timeProgress.name,
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
)
: Center(
child: FittedBox(
fit: BoxFit.fitWidth,
child: Text(
vm.timeProgress.name,
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
),
),
),
vm.hasProgressStarted
? Expanded(
flex: 2,
child: ProgressDetailCircularPercent(
percentDone: _isBeingEdited
? _editedProgress.percentDone()
: vm.timeProgress.percentDone(),
),
)
: Expanded(
flex: 2,
child: Text(
"Starts in ${vm.timeProgress.startTime.difference(DateTime.now()).inDays} Days."),
),
vm.hasProgressStarted
? Expanded(
flex: 1,
child: ProgressDetailLinearPercent(
timeProgress: _isBeingEdited
? _editedProgress
: vm.timeProgress,
),
)
: Spacer(
flex: 1,
),
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, floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: _isBeingEdited floatingActionButton: StoreConnector(
? ProgressDetailFabEditingRow( onInit: loadTimeProgressListIfUnloaded,
onSave: () => converter: (store) => timeProgressByIdSelector(store.state, args.id),
_validName ? _onSaveTimeProgress(store, args.id) : null, builder: (BuildContext context, TimeProgress timeProgress) {
onCancelEdit: () => final Store<AppState> store = StoreProvider.of<AppState>(context);
_showCancelEditTimeProgressDialog(store.state, args.id),
) void _saveEditedProgress() {
: ProgressDetailFabRow( store
onEdit: () => _onEditTimeProgress(store, args.id), .dispatch(UpdateTimeProgressAction(args.id, _editedProgress));
onDelete: () => _showDeleteTimeProgressDialog(store, args.id), _switchEditMode(false);
), }
void _deleteTimeProgress() {
store.dispatch(DeleteTimeProgressAction(args.id));
Navigator.popUntil(
context, ModalRoute.withName(HomeScreen.routeName));
}
return DetailScreenFloatingActionButtons(
editMode: _editMode,
originalProgress: timeProgress,
editedProgress: _editedProgress,
isEditedProgressValid: _isEditedProgressValid,
onEditProgress: () => _switchEditMode(true),
onSaveEditedProgress: _saveEditedProgress,
onCancelEditProgress: _cancelEditMode,
onDeleteProgress: _deleteTimeProgress);
}),
); );
} }
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
}
class _ViewModel {
final TimeProgress timeProgress;
final bool hasProgressStarted;
_ViewModel({
@required this.timeProgress,
@required this.hasProgressStarted,
});
static _ViewModel fromStoreAndArg(
Store<AppState> store, ProgressDetailScreenArguments args) {
TimeProgress tp = timeProgressByIdSelector(store.state, args.id);
return _ViewModel(
timeProgress: tp,
hasProgressStarted: DateTime.now().millisecondsSinceEpoch >
tp.startTime.millisecondsSinceEpoch);
}
} }

View File

@ -4,12 +4,29 @@ import 'package:time_progress_tracker/models/time_progress.dart';
List<TimeProgress> timeProgressListSelector(AppState state) => List<TimeProgress> timeProgressListSelector(AppState state) =>
state.timeProgressList; state.timeProgressList;
List<TimeProgress> startedTimeProgressesSelector(AppState state) => List<TimeProgress> activeTimeProgressesSelector(AppState state) {
state.timeProgressList return state.timeProgressList
.where((timeProgress) => .where((timeProgress) =>
DateTime.now().millisecondsSinceEpoch >= timeProgress.hasStarted() && !timeProgress.hasEnded())
timeProgress.startTime.millisecondsSinceEpoch)
.toList(); .toList();
}
List<TimeProgress> inactiveTimeProgressesSelector(AppState state) {
return state.timeProgressList
.where((timeProgress) =>
!timeProgress.hasStarted() || timeProgress.hasEnded())
.toList();
}
@Deprecated("use active TimeProgresses Selector instead.")
List<TimeProgress> currentTimeProgressSelector(AppState state) {
int currentTime = DateTime.now().millisecondsSinceEpoch;
return state.timeProgressList
.where((tp) =>
currentTime >= tp.startTime.millisecondsSinceEpoch &&
tp.endTime.millisecondsSinceEpoch >= currentTime)
.toList();
}
List<TimeProgress> futureTimeProgressesSelector(AppState state) => List<TimeProgress> futureTimeProgressesSelector(AppState state) =>
state.timeProgressList state.timeProgressList
@ -18,8 +35,15 @@ List<TimeProgress> futureTimeProgressesSelector(AppState state) =>
timeProgress.startTime.millisecondsSinceEpoch) timeProgress.startTime.millisecondsSinceEpoch)
.toList(); .toList();
List<TimeProgress> pastTimeProgressesSelector(AppState state) =>
state.timeProgressList
.where((tp) =>
tp.endTime.millisecondsSinceEpoch <
DateTime.now().millisecondsSinceEpoch)
.toList();
TimeProgress timeProgressByIdSelector(AppState state, String id) { TimeProgress timeProgressByIdSelector(AppState state, String id) {
if (state.timeProgressList.length < 1) return null; if (state.timeProgressList.length < 1) return null;
return state.timeProgressList return state.timeProgressList
.firstWhere((timeProgress) => timeProgress.id == id); .firstWhere((timeProgress) => timeProgress.id == id, orElse: () => null);
} }

View File

@ -1,127 +0,0 @@
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';
import 'package:time_progress_tracker/selectors/time_progress_selectors.dart';
class AppDrawer extends StatelessWidget {
final String appVersion;
AppDrawer({
Key key,
@required this.appVersion,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Drawer(
child: StoreConnector(
converter: _ViewModel.fromStore,
onInit: loadTimeProgressListIfUnloaded,
builder: (context, _ViewModel vm) {
if (!vm.hasLoaded) {
return Center(
child: CircularProgressIndicator(),
);
}
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.startedTimeProgresses.length > 0) {
for (TimeProgress tp in vm.startedTimeProgresses) {
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.startedTimeProgresses.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 $appVersion",
applicationLegalese: '\u00a9Andreas Fahrecker 2020');
},
),
));
return ListView(
children: drawerTileList,
);
},
),
);
}
}
class _ViewModel {
final List<TimeProgress> startedTimeProgresses;
final bool hasLoaded;
_ViewModel({
@required this.startedTimeProgresses,
@required this.hasLoaded,
});
static _ViewModel fromStore(Store<AppState> store) {
return _ViewModel(
startedTimeProgresses: startedTimeProgressesSelector(store.state),
hasLoaded: store.state.hasLoaded,
);
}
}

View File

@ -4,14 +4,12 @@ class AppYesNoDialog extends StatelessWidget {
final String titleText; final String titleText;
final String contentText; final String contentText;
final void Function() onYesPressed; final void Function() onYesPressed;
final void Function() onNoPressed;
AppYesNoDialog({ AppYesNoDialog({
Key key, Key key,
@required this.titleText, @required this.titleText,
@required this.contentText, @required this.contentText,
@required this.onYesPressed, @required this.onYesPressed,
@required this.onNoPressed,
}) : super(key: key); }) : super(key: key);
@override @override
@ -26,7 +24,9 @@ class AppYesNoDialog extends StatelessWidget {
), ),
FlatButton( FlatButton(
child: Text("No"), child: Text("No"),
onPressed: onNoPressed, onPressed: () {
Navigator.pop(context);
},
) )
], ],
); );

View File

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
class DatePickerBtn extends StatelessWidget {
final String leadingString;
final DateTime pickedDate;
final void Function(DateTime) onDatePicked;
DatePickerBtn({
@required this.leadingString,
@required this.pickedDate,
@required this.onDatePicked,
}) : super();
@override
Widget build(BuildContext context) {
ThemeData appTheme = Theme.of(context);
return FlatButton(
onPressed: () async {
onDatePicked(await showDatePicker(
context: context,
initialDate: pickedDate,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
));
},
child: Text(
"$leadingString ${pickedDate.toLocal().toString().split(" ")[0]}"),
color: appTheme.accentColor,
);
}
}

View File

@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/widgets/app_yes_no_dialog_widget.dart';
class DetailScreenFloatingActionButtons extends StatelessWidget {
final bool editMode, isEditedProgressValid;
final TimeProgress originalProgress, editedProgress;
final void Function() onEditProgress,
onSaveEditedProgress,
onCancelEditProgress,
onDeleteProgress;
DetailScreenFloatingActionButtons({
@required this.editMode,
@required this.originalProgress,
@required this.editedProgress,
@required this.isEditedProgressValid,
@required this.onEditProgress,
@required this.onSaveEditedProgress,
@required this.onCancelEditProgress,
@required this.onDeleteProgress,
});
@override
Widget build(BuildContext context) {
final ThemeData appTheme = Theme.of(context);
void _onCancelEditTimeProgressBTN() {
if (originalProgress == editedProgress)
onCancelEditProgress();
else {
showDialog(
context: context,
builder: (_) => AppYesNoDialog(
titleText: "Cancel Editing of ${originalProgress.name}",
contentText:
"Are you sure that you want to discard the changes done to ${originalProgress.name}",
onYesPressed: () {
onCancelEditProgress();
Navigator.pop(context);
},
),
);
}
}
void _onDeleteTimeProgressBTN() {
showDialog(
context: context,
builder: (_) => AppYesNoDialog(
titleText: "Delete ${originalProgress.name}",
contentText: "Are you sure you want to delete this time progress?",
onYesPressed: onDeleteProgress,
),
);
}
return Row(
children: [
Expanded(
child: FloatingActionButton(
heroTag:
editMode ? "saveEditedTimeProgressBTN" : "editTimeProgressBTN",
child: editMode ? Icon(Icons.save) : Icon(Icons.edit),
backgroundColor: editMode ? Colors.green : appTheme.accentColor,
onPressed: editMode
? isEditedProgressValid
? onSaveEditedProgress
: null
: onEditProgress,
),
),
Expanded(
child: FloatingActionButton(
heroTag: editMode
? "cancelEditTimeProgressBTN"
: "deleteTimeProgressBTN",
child: editMode ? Icon(Icons.cancel) : Icon(Icons.delete),
backgroundColor: Colors.red,
onPressed: editMode
? _onCancelEditTimeProgressBTN
: _onDeleteTimeProgressBTN,
),
),
],
);
}
}

View File

@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
class HomeBottomNavBar extends StatelessWidget {
final int currentIndex;
final Function onTap;
HomeBottomNavBar({
Key key,
@required this.currentIndex,
@required this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
ThemeData appTheme = Theme.of(context);
return BottomNavigationBar(
onTap: onTap,
currentIndex: currentIndex,
items: [
BottomNavigationBarItem(
icon: new Icon(
Icons.alarm,
color: appTheme.primaryColor,
),
label: "Active Progresses",
),
BottomNavigationBarItem(
icon: new Icon(
Icons.alarm_off,
color: appTheme.primaryColor,
),
label: "Inactive Progresses",
),
BottomNavigationBarItem(
icon: new Icon(
Icons.settings,
color: appTheme.primaryColor,
),
label: "Settings",
)
],
);
}
}

View File

@ -0,0 +1,44 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/screens/progress_detail_screen.dart';
class HomeProgressListTile extends StatelessWidget {
final TimeProgress timeProgress;
HomeProgressListTile({
Key key,
@required this.timeProgress,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Widget listTileSubTitle;
if (timeProgress.hasStarted() && !timeProgress.hasEnded())
listTileSubTitle = LinearPercentIndicator(
center: Text("${(timeProgress.percentDone() * 100).floor()} %"),
percent: timeProgress.percentDone(),
progressColor: Colors.green,
backgroundColor: Colors.red,
lineHeight: 20,
);
if (!timeProgress.hasStarted())
listTileSubTitle = Text(
"Starts in ${timeProgress.startTime.difference(DateTime.now()).inDays} Days");
if (timeProgress.hasEnded())
listTileSubTitle = Text(
"Ended ${DateTime.now().difference(timeProgress.endTime).inDays} Days ago.");
return Card(
child: ListTile(
title: Text(timeProgress.name),
subtitle: listTileSubTitle,
onTap: () {
Navigator.pushNamed(context, ProgressDetailScreen.routeName,
arguments: ProgressDetailScreenArguments(timeProgress.id));
},
),
);
}
}

View File

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/selectors/time_progress_selectors.dart';
import 'package:time_progress_tracker/widgets/home/home_progress_list_tile.dart';
class HomeActiveProgressesTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreConnector(
onInit: loadTimeProgressListIfUnloaded,
converter: (store) => store.state.hasLoaded,
builder: (BuildContext context, dynamic hasLoaded) {
if (!(hasLoaded as bool))
return Center(
child: CircularProgressIndicator(),
);
return StoreConnector(
onInit: loadTimeProgressListIfUnloaded,
converter: (store) => activeTimeProgressesSelector(store.state),
builder: (BuildContext context, List<TimeProgress> timeProgresses) {
if (timeProgresses.length < 1)
return Container(
padding: EdgeInsets.all(16),
child: Center(
child: Text(
"You don't have any currently active time progresses, that are tracked."),
),
);
return ListView(
padding: EdgeInsets.all(8),
children: timeProgresses
.map((timeProgress) => HomeProgressListTile(
timeProgress: timeProgress,
))
.toList(),
);
},
);
},
);
}
}

View File

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/selectors/time_progress_selectors.dart';
import 'package:time_progress_tracker/widgets/home/home_progress_list_tile.dart';
class HomeInactiveProgressesTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreConnector(
onInit: loadTimeProgressListIfUnloaded,
converter: (store) => store.state.hasLoaded,
builder: (BuildContext context, dynamic hasLoaded) {
if (!(hasLoaded as bool))
return Center(
child: CircularProgressIndicator(),
);
return StoreConnector(
onInit: loadTimeProgressListIfUnloaded,
converter: (store) => inactiveTimeProgressesSelector(store.state),
builder: (BuildContext context, List<TimeProgress> timeProgresses) {
if (timeProgresses.length < 1)
return Container(
padding: EdgeInsets.all(16),
child: Center(
child: Text(
"You don't have any currently inactive time progresses, that are tracked."),
),
);
return ListView(
padding: EdgeInsets.all(8),
children: timeProgresses
.map((timeProgress) =>
HomeProgressListTile(timeProgress: timeProgress))
.toList(),
);
},
);
},
);
}
}

View File

@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
import 'package:package_info/package_info.dart';
import 'package:time_progress_tracker/app.dart';
class HomeSettingsTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16),
child: Center(
child: Column(
children: [
Text("The Settings of this App are not yet implemented."),
FlatButton(
onPressed: () {
showAboutDialog(
context: context,
applicationName: TimeProgressTrackerApp.name,
applicationVersion: "Beta",
applicationLegalese: '\u00a9Andreas Fahrecker 2020-2021');
},
child: Text("About"))
],
),
),
);
}
}

View File

@ -1,21 +0,0 @@
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

@ -1,44 +0,0 @@
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

@ -1,35 +0,0 @@
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

@ -1,33 +0,0 @@
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

@ -1,24 +0,0 @@
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

@ -1,32 +0,0 @@
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

@ -0,0 +1,124 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/widgets/date_picker_btn.dart';
class ProgressEditorWidget extends StatefulWidget {
final TimeProgress timeProgress;
final Function(TimeProgress, bool) onTimeProgressChanged;
ProgressEditorWidget({
@required this.timeProgress,
@required this.onTimeProgressChanged,
});
@override
State<StatefulWidget> createState() {
return _ProgressEditorWidgetState();
}
}
class _ProgressEditorWidgetState extends State<ProgressEditorWidget> {
final _nameTextController = TextEditingController();
bool _validName = true, _validDate = true;
void _onNameChanged() {
TimeProgress newProgress =
widget.timeProgress.copyWith(name: _nameTextController.text);
widget.onTimeProgressChanged(
newProgress, TimeProgress.isValid(newProgress));
setState(() {
_validName = TimeProgress.isNameValid(newProgress.name);
});
}
void _onStartDateChanged(DateTime newStartDate) {
TimeProgress newProgress =
widget.timeProgress.copyWith(startTime: newStartDate);
widget.onTimeProgressChanged(
newProgress, TimeProgress.isValid(newProgress));
setState(() {
_validDate =
TimeProgress.areTimesValid(newStartDate, newProgress.endTime);
});
}
void _onEndDateChanged(DateTime newEndDate) {
TimeProgress newProgress =
widget.timeProgress.copyWith(endTime: newEndDate);
widget.onTimeProgressChanged(
newProgress, TimeProgress.isValid(newProgress));
setState(() {
_validDate =
TimeProgress.areTimesValid(newProgress.startTime, newEndDate);
});
}
@override
void initState() {
_nameTextController.text = widget.timeProgress.name;
_nameTextController.addListener(_onNameChanged);
super.initState();
}
@override
Widget build(BuildContext context) {
List<Widget> columnChildren = [
Expanded(
child: TextField(
controller: _nameTextController,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Progress Name",
errorText: _validName
? null
: "The Name need to have at least 3 and at max 20 symbols.",
),
),
),
Expanded(
child: Row(
children: [
Expanded(
child: Padding(
padding: EdgeInsets.only(right: 5),
child: DatePickerBtn(
leadingString: "Start Date:",
pickedDate: widget.timeProgress.startTime,
onDatePicked: _onStartDateChanged,
),
),
),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 5),
child: DatePickerBtn(
leadingString: "End Date:",
pickedDate: widget.timeProgress.endTime,
onDatePicked: _onEndDateChanged,
),
),
),
],
),
)
];
if (!_validDate)
columnChildren.add(
Expanded(
child: Center(
child: Text(
"Invalid Dates. The Start Date has to be before the End Date",
style: TextStyle(color: Colors.red),
),
),
),
);
return Container(
child: Column(
children: columnChildren,
),
);
}
}

View File

@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
class ProgressViewWidget extends StatelessWidget {
final TimeProgress timeProgress;
ProgressViewWidget({
@required this.timeProgress,
});
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Expanded(
child: FittedBox(
fit: BoxFit.fitWidth,
child: Text(
timeProgress.name,
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
),
),
Expanded(
child: CircularPercentIndicator(
radius: 100,
lineWidth: 10,
percent: timeProgress.percentDone(),
progressColor: Colors.green,
backgroundColor: Colors.red,
center: Text("${(timeProgress.percentDone() * 100).floor()} %"),
),
),
Expanded(
child: LinearPercentIndicator(
padding: EdgeInsets.symmetric(horizontal: 15),
percent: timeProgress.percentDone(),
leading: Text("${timeProgress.daysBehind()} Days"),
center: Text("${(timeProgress.percentDone() * 100).floor()} %"),
trailing: Text("${timeProgress.daysLeft()} Days"),
progressColor: Colors.green,
backgroundColor: Colors.red,
lineHeight: 25,
),
),
],
),
);
}
}

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.5.0-nullsafety.1" version: "2.5.0"
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.1.0-nullsafety.1" version: "2.1.0"
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.1.0-nullsafety.3" version: "1.1.0"
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.2.0-nullsafety.1" version: "1.2.0"
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.1.0-nullsafety.1" version: "1.1.0"
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.15.0-nullsafety.3" version: "1.15.0"
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.2.0-nullsafety.1" version: "1.2.0"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
@ -134,7 +134,7 @@ packages:
name: image name: image
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.18" version: "2.1.19"
intl: intl:
dependency: transitive dependency: transitive
description: description:
@ -142,34 +142,41 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.16.1" version: "0.16.1"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.10-nullsafety.1" version: "0.12.10"
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.3.0-nullsafety.3" version: "1.3.0"
package_info: package_info:
dependency: "direct main" dependency: "direct main"
description: description:
name: package_info name: package_info
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.3+2" version: "0.4.3+4"
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.8.0-nullsafety.1" version: "1.8.0"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@ -197,7 +204,7 @@ packages:
name: percent_indicator name: percent_indicator
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.8" version: "2.1.9+1"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -274,7 +281,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+3" version: "0.0.2+3"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -286,63 +293,63 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0-nullsafety.2" version: "1.8.0"
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.10.0-nullsafety.1" version: "1.10.0"
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.1.0-nullsafety.1" version: "2.1.0"
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.1.0-nullsafety.1" version: "1.1.0"
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.2.0-nullsafety.1" version: "1.2.0"
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.19-nullsafety.2" version: "0.2.19"
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.3.0-nullsafety.3" version: "1.3.0"
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.1.0-nullsafety.3" version: "2.1.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.7.3" version: "1.7.4+1"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -365,5 +372,5 @@ packages:
source: hosted source: hosted
version: "2.2.1" version: "2.2.1"
sdks: sdks:
dart: ">=2.10.0-110 <2.11.0" dart: ">=2.12.0-0.0 <3.0.0"
flutter: ">=1.12.13+hotfix.5 <2.0.0" flutter: ">=1.12.13+hotfix.5"

View File

@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.0.3+3 version: 0.0.12+12
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: ">=2.7.0 <3.0.0"