24 Commits

Author SHA1 Message Date
08db53db20 Updated Flutter SDK for Null-Safety
Ported Progress Detail Screen to PlatformScaffold

Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-18 18:34:29 +01:00
b1a90b1e05 Added tpId parameter to Detail Screen
List Item in cupertino now acceptable

Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-18 17:20:09 +01:00
e71b65bdf3 Renamed Theme Utils and work on Progress List Item
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-18 16:05:09 +01:00
d6ca3d4270 Created PlatformActionButton
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-16 16:46:54 +01:00
aabeef6384 Started Porting Progress Creation Screen
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-16 16:33:46 +01:00
45d4f7ba3a Ported Create Progress Button for Platform Aware
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-16 15:16:50 +01:00
e889f93d5c Cleaned up the repo
Singed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-13 19:47:20 +01:00
fa2cd4c192 Moved App State to redux
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-13 18:24:49 +01:00
e5e924b44a Moved helper_functions to utils
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-13 18:24:00 +01:00
beb871a60e Created Project readme
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-12 00:33:22 +01:00
c43316be6d Implemented New Screens in Dashboard Screen
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-10 21:10:05 +01:00
ddf9e981fd Created SettingsScreen
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-10 21:09:39 +01:00
32432bb138 Started using Platform Text
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-10 21:09:16 +01:00
df46901f6d Created ActiveTimeProgressesScreen
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-10 21:08:45 +01:00
b813481369 Created ActiveTimeProgressesScreen and migrated ListView and Item to Platform Specific
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-09 20:12:26 +01:00
ed5d2b92f7 Started implementing flutter_platform_widgets
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-09 19:32:39 +01:00
40bdcc44f9 Feature/code cleanup (#9)
* Implemented hasSettingsLoaded reducer
* Added Padding to Progress List View
* Created Settings and Time Progress List Store Connector
* Rewritten Home Active Tab
* Fixed missing onTap in Progress List Tile
* Started using new Store Connectors in Inactive and Settings Tab
* Created Time Progress Store Connector
* Rewritten ProgressDetailScreen with new Store Connectors
* Rewritten DatePickerBtn with TextButton
* Deleted unused widget
* Changed Foreground Color behaviour in ColorPicker BTN
* Created Select Duration Button
* Rewritten Duration Setting Widget
* Updated Version Number

Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-08 20:48:45 +01:00
fc35476503 Feature/widget testing (#8)
* Configured iOS Build
* Created ProgressListTile widget
* Created MaterialTesterWidget
* Created String Methods for Testing in ProgressListTile
* Created ProgressListTileStrings class
* Using Progress List Tile
* Created Progress List View
* Created Progress List Tile currently, future and past test.
* Created Progress List View one and five Time Progresses test.

Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-08 16:19:31 +01:00
90f2998088 Feature/default duration setting (#7)
* Implemented Basic Duration Settings into AppSettings Model

* Implemented Basic Duration Settings into AppSettings Model

* Created Duration Settings Widget and Started using ViewModel in HomeSettingsTab

* Updated Version Number
2021-03-03 19:59:33 +01:00
b520d56d1a Feature/change progress colors (#6)
* Added Settings Actions
* Created App Settings and Repo + Entity
* Code cleanup Time Progress
* Created App Settings Middleware
* Has Progresses ad has Settings loaded
* Created Load and Update Settings reducers
* Added Settings store middleware to renamed store middleware
* Load Default Settings if not Saved. Use Redux AppState to showprogress colors.
Colors are not yet changeable.
* Added ColorPicker for Done and Left Color
Fixed Loading App Settings Bug
* Fixed Version Number
* Fixed Android App Logo
* Extracted Color Settings into Widget
* Fixed Home Settings Tab Layout and Color Settings Button now show Text in complementary color
2021-03-03 16:35:08 +01:00
c580e45361 Fixed App Logo
Signed-of-by : Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-24 02:06:34 +01:00
238fd6bc6a Merge remote-tracking branch 'origin/main' into main 2021-02-23 23:15:49 +01:00
478ea644a9 Added new App Icon
Signed-of-by : Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-23 23:15:31 +01:00
82fc8c5fbb Added new App Icon
Signed-of-by : Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-02-23 23:14:51 +01:00
115 changed files with 2478 additions and 1020 deletions

View File

@ -1,8 +1,28 @@
# time_progress_tracker # Time Progress Tracker
A Flutter Application to create Timers with a percentage indicator. A Flutter Application to create Timers with a percentage indicator.
The Idea for this Application came to me while, I was doing my civil service.
It is a really simple app at this state. You can enter Time Progresses, which are made up of
a name, a start date, and a end date.
Then you can see a list of all currently active and a list of all currently inactive progresses,
including their current percentages.
## Getting Started ## Current State of the repo.
Now the repo is mostly cleaned up.
My own model classes are located in lib/models.
The logic for converting the models to json format and saving them is in lib/persistence.
All redux logic, including store connector widgets are in lib/redux.
The Flutter UI widgets are located in lib/ui.
Other stuff is in lib/utils or directly in lib.
- [Google Play](https://play.google.com/store/apps/details?id=com.fahrecker.time_progress_calculator)
## Original Readme
### Getting Started
This project is a starting point for a Flutter application. This project is a starting point for a Flutter application.

View File

@ -0,0 +1,6 @@
package com.fahrecker.time_progress_tracker
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

BIN
assets/logo/android_1024.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

BIN
assets/logo/ios_1024.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View File

@ -1,4 +1,5 @@
flutter_icons: flutter_icons:
android: true android: true
ios: true ios: true
image_path: "assets/icons/launcher_icon.png" image_path_android: "assets/logo/android_1024.png"
image_path_ios: "assets/logo/ios_1024.png"

View File

@ -3,7 +3,7 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>en</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>App</string> <string>App</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>

View File

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

View File

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

41
ios/Podfile Normal file
View File

@ -0,0 +1,41 @@
# Uncomment this line to define a global platform for your project
platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

View File

@ -3,15 +3,14 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 46; objectVersion = 51;
objects = { objects = {
/* 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 */; }; 6186AFFAE7FCA76C81CF360E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7D5EAE1ADE1FFBE7D23EE84E /* Pods_Runner.framework */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
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 */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@ -33,22 +32,21 @@
/* 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>"; }; 1DBA7F16BF734A3CE98E5546 /* 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>"; };
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>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; 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>"; }; 7D5EAE1ADE1FFBE7D23EE84E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
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; }; B4DFA246891E5346CCC8628F /* 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>"; };
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>"; }; DC45E65269FD602449E1FEBD /* 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 */
@ -56,19 +54,21 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
4D431DF94830EB4E4109ECB2 /* libPods-Runner.a in Frameworks */, 6186AFFAE7FCA76C81CF360E /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
79DE0847F7F4B7CF08AEE47D /* Frameworks */ = { 7425B1E33E3BCBE464E2CBB5 /* Pods */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A0182DC80C66D064A11FDF0B /* libPods-Runner.a */, B4DFA246891E5346CCC8628F /* Pods-Runner.debug.xcconfig */,
1DBA7F16BF734A3CE98E5546 /* Pods-Runner.release.xcconfig */,
DC45E65269FD602449E1FEBD /* Pods-Runner.profile.xcconfig */,
); );
name = Frameworks; path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
9740EEB11CF90186004384FC /* Flutter */ = { 9740EEB11CF90186004384FC /* Flutter */ = {
@ -88,8 +88,8 @@
9740EEB11CF90186004384FC /* Flutter */, 9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
9DBFAAD22EAE0F654D097AD5 /* Pods */, 7425B1E33E3BCBE464E2CBB5 /* Pods */,
79DE0847F7F4B7CF08AEE47D /* Frameworks */, E3A24E042363B3BCA7910470 /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -104,36 +104,24 @@
97C146F01CF9000F007C117D /* Runner */ = { 97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */, 97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
); );
path = Runner; path = Runner;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
97C146F11CF9000F007C117D /* Supporting Files */ = { E3A24E042363B3BCA7910470 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
97C146F21CF9000F007C117D /* main.m */, 7D5EAE1ADE1FFBE7D23EE84E /* Pods_Runner.framework */,
); );
name = "Supporting Files"; name = Frameworks;
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>"; sourceTree = "<group>";
}; };
/* End PBXGroup section */ /* End PBXGroup section */
@ -143,13 +131,14 @@
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 */, A60954191C254DCAC69F1735 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */, 9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */, 97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */, 97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */, 97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
555CAADD55712EED12802136 /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@ -171,6 +160,7 @@
TargetAttributes = { TargetAttributes = {
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1; CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
}; };
}; };
}; };
@ -221,6 +211,23 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
}; };
555CAADD55712EED12802136 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -235,7 +242,7 @@
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 */ = { A60954191C254DCAC69F1735 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
@ -264,8 +271,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
97C146F31CF9000F007C117D /* main.m in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -333,7 +339,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -347,20 +353,19 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = RD9K843SK5;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = (
LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.fahrecker.timeProgressCalculator; PRODUCT_BUNDLE_IDENTIFIER = com.fahrecker.timeProgressTracker;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
}; };
name = Profile; name = Profile;
@ -412,7 +417,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -461,10 +466,12 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
@ -475,20 +482,20 @@
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = RD9K843SK5;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = (
LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.fahrecker.timeProgressCalculator; PRODUCT_BUNDLE_IDENTIFIER = com.fahrecker.timeProgressTracker;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
}; };
name = Debug; name = Debug;
@ -498,20 +505,19 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = RD9K843SK5;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = (
LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "@executable_path/Frameworks",
); );
PRODUCT_BUNDLE_IDENTIFIER = com.fahrecker.timeProgressCalculator; PRODUCT_BUNDLE_IDENTIFIER = com.fahrecker.timeProgressTracker;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
}; };
name = Release; name = Release;

View File

@ -1,6 +0,0 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : FlutterAppDelegate
@end

View File

@ -1,13 +0,0 @@
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end

View File

@ -0,0 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -4,6 +4,8 @@
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Time Progress Tracker</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@ -11,7 +13,7 @@
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>time_progress_calculator</string> <string>time_progress_tracker</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>

View File

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@ -1,9 +0,0 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

View File

@ -1,38 +0,0 @@
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 TimeProgressListLoadedAction {
final List<TimeProgress> timeProgressList;
TimeProgressListLoadedAction(this.timeProgressList);
}
class TimeProgressListNotLoadedAction {}
class AddTimeProgressAction {
final TimeProgress timeProgress;
AddTimeProgressAction(this.timeProgress);
}
class UpdateTimeProgressAction {
final String id;
final TimeProgress updatedTimeProgress;
UpdateTimeProgressAction(this.id, this.updatedTimeProgress);
}
class DeleteTimeProgressAction {
final String id;
DeleteTimeProgressAction(this.id);
}
void loadTimeProgressListIfUnloaded(Store<AppState> store) {
if (!store.state.hasLoaded) {
store.dispatch(LoadTimeProgressListAction());
}
}

View File

@ -1,10 +1,11 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.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/models/app_state.dart'; import 'package:time_progress_tracker/redux/app_state.dart';
import 'package:time_progress_tracker/screens/progress_creation_screen.dart'; import 'package:time_progress_tracker/ui/screens/dashboard_screen.dart';
import 'package:time_progress_tracker/screens/home_screen.dart'; import 'package:time_progress_tracker/utils/theme_utils.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";
@ -12,31 +13,19 @@ class TimeProgressTrackerApp extends StatelessWidget {
final Store<AppState> store; final Store<AppState> store;
TimeProgressTrackerApp({ TimeProgressTrackerApp({
Key key, Key? key,
this.store, required this.store,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StoreProvider( return StoreProvider(
store: store, store: store,
child: MaterialApp( child: PlatformApp(
title: name, title: name,
theme: ThemeData( home: DashboardScreen(),
primarySwatch: Colors.indigo, material: (_, __) => MaterialAppData(theme: materialThemeData),
accentColor: Colors.indigoAccent, cupertino: (_, __) => CupertinoAppData(theme: cupertinoThemeData),
brightness: Brightness.light,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: HomeScreen.routeName,
routes: {
HomeScreen.routeName: (BuildContext context) =>
HomeScreen(),
ProgressDetailScreen.routeName: (BuildContext context) =>
ProgressDetailScreen(),
ProgressCreationScreen.routeName: (BuildContext context) =>
ProgressCreationScreen(),
},
), ),
); );
} }

View File

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

View File

@ -1,49 +0,0 @@
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/persistence/time_progress_entity.dart';
import 'package:time_progress_tracker/persistence/time_progress_repository.dart';
import 'package:time_progress_tracker/selectors/time_progress_selectors.dart';
List<Middleware<AppState>> createStoreTimeProgressListMiddleware(
TimeProgressRepository repository) {
final saveTimeProgressList = _createSaveTimeProgressList(repository);
final loadTimeProgressList = _createLoadTimeProgressList(repository);
return [
TypedMiddleware<AppState, LoadTimeProgressListAction>(loadTimeProgressList),
TypedMiddleware<AppState, AddTimeProgressAction>(saveTimeProgressList),
TypedMiddleware<AppState, UpdateTimeProgressAction>(saveTimeProgressList),
TypedMiddleware<AppState, DeleteTimeProgressAction>(saveTimeProgressList),
];
}
Middleware<AppState> _createSaveTimeProgressList(
TimeProgressRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
next(action);
repository.saveTimeProgressList(
timeProgressListSelector(store.state)
.map<TimeProgressEntity>((timeProgress) => timeProgress.toEntity())
.toList(growable: false),
);
};
}
Middleware<AppState> _createLoadTimeProgressList(
TimeProgressRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
repository.loadTimeProgressList().then((timeProgresses) {
List<TimeProgress> timeProgressList =
timeProgresses.map<TimeProgress>(TimeProgress.fromEntity).toList();
if (timeProgressList == null) {
timeProgressList = [];
}
store.dispatch(TimeProgressListLoadedAction(
timeProgressList,
));
}).catchError((_) => store.dispatch(TimeProgressListNotLoadedAction()));
};
}

View File

@ -14,5 +14,18 @@ class TimeProgressStartTimeIsNotBeforeEndTimeException implements Exception {
this.startTime, this.endTime); this.startTime, this.endTime);
String errMsg() => String errMsg() =>
"The Start Time has to be before the end time. Therefore these values are invalid: Start Time: $startTime EndTime: $endTime"; "The Start Time has to be before the end time. Therefore these values are"
" invalid: Start Time: $startTime EndTime: $endTime";
}
class TimeProgressHasStartedException implements Exception {
String errMsg() =>
"This TimeProgress has started. Therefore all calculation, which assume, "
"that the progress hasn't started yet can't be performed";
}
class TimeProgressHasNotEndedException implements Exception {
String errMsg() =>
"This TimeProgress hasn't ended. Therefore all calculation, which assume,"
" that the progress has ended already can't be performed";
} }

View File

@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/persistence/app_settings.dart';
@immutable
class AppSettings {
final Color doneColor;
final Color leftColor;
final Duration duration;
const AppSettings({
required this.doneColor,
required this.leftColor,
required this.duration,
});
AppSettings copyWith({
Color? doneColor,
Color? leftColor,
Duration? duration,
}) =>
AppSettings(
doneColor: doneColor ?? this.doneColor,
leftColor: leftColor ?? this.leftColor,
duration: duration ?? this.duration,
);
@override
int get hashCode =>
doneColor.hashCode ^ leftColor.hashCode ^ duration.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is AppSettings &&
runtimeType == other.runtimeType &&
doneColor == other.doneColor &&
leftColor == other.leftColor &&
duration == other.duration;
AppSettingsEntity toEntity() =>
AppSettingsEntity(doneColor.value, leftColor.value, duration.inDays);
static AppSettings fromEntity(AppSettingsEntity entity) => AppSettings(
doneColor: Color(entity.doneColorValue),
leftColor: Color(entity.leftColorValue),
duration: Duration(days: entity.durationDays),
);
}

View File

@ -1,36 +0,0 @@
import 'package:meta/meta.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
@immutable
class AppState {
final bool hasLoaded;
final List<TimeProgress> timeProgressList;
AppState({
this.hasLoaded = false,
this.timeProgressList = const [],
});
factory AppState.initial() => AppState(hasLoaded: false);
AppState copyWith({
bool hasLoaded,
List<TimeProgress> timeProgressList,
}) {
return AppState(
hasLoaded: hasLoaded ?? this.hasLoaded,
timeProgressList: timeProgressList ?? this.timeProgressList,
);
}
@override
int get hashCode => hasLoaded.hashCode ^ timeProgressList.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is AppState &&
runtimeType == other.runtimeType &&
hasLoaded == other.hasLoaded &&
timeProgressList == other.timeProgressList;
}

View File

@ -1,6 +1,6 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:time_progress_tracker/models/app_exceptions.dart'; import 'package:time_progress_tracker/models/app_exceptions.dart';
import 'package:time_progress_tracker/persistence/time_progress_entity.dart'; import 'package:time_progress_tracker/persistence/time_progress.dart';
import 'package:time_progress_tracker/uuid.dart'; import 'package:time_progress_tracker/uuid.dart';
@immutable @immutable
@ -10,7 +10,7 @@ class TimeProgress {
final DateTime startTime; final DateTime startTime;
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();
factory TimeProgress.initialDefault() { factory TimeProgress.initialDefault() {
@ -19,27 +19,23 @@ class TimeProgress {
"Initial Name", DateTime(thisYear - 1), DateTime(thisYear + 1)); "Initial Name", DateTime(thisYear - 1), DateTime(thisYear + 1));
} }
factory TimeProgress.defaultFromDuration(Duration duration) =>
TimeProgress("", DateTime.now(), DateTime.now().add(duration));
TimeProgress copyWith( TimeProgress copyWith(
{String id, String name, DateTime startTime, DateTime endTime}) { {String? id, String? name, DateTime? startTime, DateTime? endTime}) =>
return TimeProgress( TimeProgress(
name ?? this.name, name ?? this.name,
startTime ?? this.startTime, startTime ?? this.startTime,
endTime ?? this.endTime, endTime ?? this.endTime,
id: id ?? this.id, id: id ?? this.id,
); );
}
int daysBehind() { int daysBehind() => DateTime.now().difference(startTime).inDays;
return DateTime.now().difference(startTime).inDays;
}
int daysLeft() { int daysLeft() => endTime.difference(DateTime.now()).inDays;
return endTime.difference(DateTime.now()).inDays;
}
int allDays() { int allDays() => endTime.difference(startTime).inDays;
return endTime.difference(startTime).inDays;
}
double percentDone() { double percentDone() {
double percent = this.daysBehind() / (this.allDays() / 100) / 100; double percent = this.daysBehind() / (this.allDays() / 100) / 100;
@ -48,14 +44,20 @@ class TimeProgress {
return percent; return percent;
} }
bool hasStarted() { bool hasStarted() =>
return DateTime.now().millisecondsSinceEpoch > DateTime.now().millisecondsSinceEpoch > startTime.millisecondsSinceEpoch;
startTime.millisecondsSinceEpoch;
int daysTillStart() {
if (hasStarted()) throw new TimeProgressHasStartedException();
return startTime.difference(DateTime.now()).inDays;
} }
bool hasEnded() { bool hasEnded() =>
return DateTime.now().millisecondsSinceEpoch > DateTime.now().millisecondsSinceEpoch > endTime.millisecondsSinceEpoch;
endTime.millisecondsSinceEpoch;
int daysSinceEnd() {
if (!hasEnded()) throw new TimeProgressHasNotEndedException();
return DateTime.now().difference(endTime).inDays;
} }
@override @override
@ -73,9 +75,8 @@ class TimeProgress {
endTime == other.endTime; endTime == other.endTime;
@override @override
String toString() { String toString() =>
return "TimeProgress{id: $id, name: $name, startTime: $startTime, endTime: $endTime}"; "TimeProgress{id: $id, name: $name, startTime: $startTime, endTime: $endTime}";
}
TimeProgressEntity toEntity() { TimeProgressEntity toEntity() {
if (!TimeProgress.isNameValid(name)) if (!TimeProgress.isNameValid(name))
@ -86,25 +87,20 @@ class TimeProgress {
return TimeProgressEntity(id, name, startTime, endTime); return TimeProgressEntity(id, name, startTime, endTime);
} }
static TimeProgress fromEntity(TimeProgressEntity entity) { static TimeProgress fromEntity(TimeProgressEntity entity) => TimeProgress(
return TimeProgress( entity.name,
entity.name, entity.startTime,
entity.startTime, entity.endTime,
entity.endTime, id: entity.id,
id: entity.id ?? Uuid().generateV4(), );
);
}
static bool isValid(TimeProgress tp) { static bool isValid(TimeProgress tp) =>
return TimeProgress.isNameValid(tp.name) && TimeProgress.isNameValid(tp.name) &&
TimeProgress.areTimesValid(tp.startTime, tp.endTime); TimeProgress.areTimesValid(tp.startTime, tp.endTime);
}
static bool isNameValid(String name) { static bool isNameValid(String name) =>
return name != null && name != "" && name.length > 2 && name.length < 21; name != "" && name.length > 2 && name.length < 21;
}
static bool areTimesValid(DateTime startTime, DateTime endTime) { static bool areTimesValid(DateTime startTime, DateTime endTime) =>
return startTime.isBefore(endTime); startTime.isBefore(endTime);
}
} }

View File

@ -0,0 +1,59 @@
import 'package:shared_preferences/shared_preferences.dart';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/persistence/repository.dart';
import 'package:time_progress_tracker/utils/constants.dart';
class AppSettingsRepository extends Repository<AppSettingsEntity> {
static const String _key = "app_settings";
AppSettingsRepository(SharedPreferences prefs) : super(prefs);
@override
Future<AppSettingsEntity> load() {
final String? jsonString = this.prefs.getString(_key);
if (jsonString == null)
return Future<AppSettingsEntity>.value(AppSettingsEntity.defaults());
return Future<AppSettingsEntity>.value(
AppSettingsEntity.fromJson(codec.decode(jsonString)));
}
@override
Future<bool> save(AppSettingsEntity appSettings) =>
this.prefs.setString(_key, codec.encode(appSettings));
}
class AppSettingsEntity {
static const String _doneKey = "doneColorValue",
_leftKey = "leftColorValue",
_durationDaysKey = "durationDays";
final int doneColorValue, leftColorValue, durationDays;
AppSettingsEntity(
this.doneColorValue, this.leftColorValue, this.durationDays);
factory AppSettingsEntity.defaults() => defaultAppSettings.toEntity();
@override
int get hashCode => doneColorValue.hashCode ^ leftColorValue.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is AppSettingsEntity &&
runtimeType == other.runtimeType &&
doneColorValue == other.doneColorValue &&
leftColorValue == other.leftColorValue;
Map<String, Object> toJson() => {
_doneKey: doneColorValue,
_leftKey: leftColorValue,
_durationDaysKey: durationDays,
};
static AppSettingsEntity fromJson(Map<String, Object> json) =>
AppSettingsEntity(
json[_doneKey] as int,
json[_leftKey] as int,
json[_durationDaysKey] as int,
);
}

View File

@ -0,0 +1,14 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
abstract class Repository<T> {
final SharedPreferences prefs;
final JsonCodec codec;
Repository(this.prefs, {this.codec = json});
Future<T> load();
Future<bool> save(T e);
}

View File

@ -0,0 +1,74 @@
import 'package:shared_preferences/shared_preferences.dart';
import 'package:time_progress_tracker/persistence/repository.dart';
class TimeProgressRepository extends Repository<List<TimeProgressEntity>> {
static const String _key = "time_progress_repo";
TimeProgressRepository(SharedPreferences prefs) : super(prefs);
@override
Future<List<TimeProgressEntity>> load() {
final String? jsonString = this.prefs.getString(_key);
if (jsonString == null) {
return Future<List<TimeProgressEntity>>.value([]);
}
return Future<List<TimeProgressEntity>>.value(codec
.decode(jsonString)["timers"]
.cast<Map<String, Object>>()
.map<TimeProgressEntity>(TimeProgressEntity.fromJson)
.toList(growable: false));
}
@override
Future<bool> save(List<TimeProgressEntity> timeProgressList) {
final String jsonString = codec.encode(
{"timers": timeProgressList.map((timer) => timer.toJson()).toList()});
return this.prefs.setString(_key, jsonString);
}
}
class TimeProgressEntity {
static const String _idKey = "id",
_nameKey = "name",
_startTimeKey = "startTime",
_endTimeKey = "endTime";
final String id;
final String name;
final DateTime startTime;
final DateTime endTime;
TimeProgressEntity(this.id, this.name, this.startTime, this.endTime);
@override
int get hashCode =>
id.hashCode ^ name.hashCode ^ startTime.hashCode ^ endTime.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is TimeProgressEntity &&
runtimeType == other.runtimeType &&
id == other.id &&
name == other.name &&
startTime == other.startTime &&
endTime == other.endTime;
Map<String, Object> toJson() {
return {
_idKey: id,
_nameKey: name,
_startTimeKey: startTime.millisecondsSinceEpoch,
_endTimeKey: endTime.millisecondsSinceEpoch
};
}
static TimeProgressEntity fromJson(Map<String, Object> json) {
final String id = json[_idKey] as String;
final String name = json[_nameKey] as String;
final DateTime startTime =
DateTime.fromMillisecondsSinceEpoch(json[_startTimeKey] as int);
final DateTime endTime =
DateTime.fromMillisecondsSinceEpoch(json[_endTimeKey] as int);
return TimeProgressEntity(id, name, startTime, endTime);
}
}

View File

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

View File

@ -1,31 +0,0 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:time_progress_tracker/persistence/time_progress_entity.dart';
import 'dart:developer' as developer;
class TimeProgressRepository {
static const String _key = "time_progress_repo";
final SharedPreferences prefs;
final JsonCodec codec;
TimeProgressRepository(this.prefs, {this.codec = json});
Future<List<TimeProgressEntity>> loadTimeProgressList() {
final String jsonString = this.prefs.getString(_key);
if (jsonString == null) {
return Future<List<TimeProgressEntity>>.value([]);
}
return Future<List<TimeProgressEntity>>.value(codec
.decode(jsonString)["timers"]
.cast<Map<String, Object>>()
.map<TimeProgressEntity>(TimeProgressEntity.fromJson)
.toList(growable: false));
}
Future<bool> saveTimeProgressList(List<TimeProgressEntity> timeProgressList) {
final String jsonString = codec.encode(
{"timers": timeProgressList.map((timer) => timer.toJson()).toList()});
return this.prefs.setString(_key, jsonString);
}
}

View File

@ -1,10 +0,0 @@
import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_tracker/reducers/has_loaded_reducer.dart';
import 'package:time_progress_tracker/reducers/time_progress_list_reducer.dart';
AppState appStateReducer(AppState state, dynamic action) {
return AppState(
hasLoaded: hasLoadedReducer(state.hasLoaded, action),
timeProgressList: timeProgressListReducer(state.timeProgressList, action),
);
}

View File

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

View File

@ -1,45 +0,0 @@
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>>([
TypedReducer<List<TimeProgress>, TimeProgressListLoadedAction>(
_setLoadedTimeProgressList),
TypedReducer<List<TimeProgress>, TimeProgressListNotLoadedAction>(
_setEmptyTimeProgressList),
TypedReducer<List<TimeProgress>, AddTimeProgressAction>(_addTimeProgress),
TypedReducer<List<TimeProgress>, UpdateTimeProgressAction>(
_updateTimeProgress),
TypedReducer<List<TimeProgress>, DeleteTimeProgressAction>(_deleteTimeProgress),
]);
List<TimeProgress> _setLoadedTimeProgressList(
List<TimeProgress> timeProgressList, TimeProgressListLoadedAction action) {
return action.timeProgressList;
}
List<TimeProgress> _setEmptyTimeProgressList(
List<TimeProgress> timeProgressList, TimeProgressListNotLoadedAction action) {
return [];
}
List<TimeProgress> _addTimeProgress(
List<TimeProgress> timeProgressList, AddTimeProgressAction action) {
return List.from(timeProgressList)
..add(action.timeProgress)
..toList(growable: false);
}
List<TimeProgress> _updateTimeProgress(
List<TimeProgress> timeProgressList, UpdateTimeProgressAction action) {
return timeProgressList
.map((timeProgress) => timeProgress.id == action.id
? action.updatedTimeProgress
: timeProgress)
.toList(growable: false);
}
List<TimeProgress> _deleteTimeProgress(
List<TimeProgress> timeProgressList, DeleteTimeProgressAction action) {
return timeProgressList.where((timeProgress) => timeProgress.id != action.id).toList(growable: false);
}

View File

@ -0,0 +1,14 @@
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/redux/actions/redux_actions.dart';
class LoadAppSettingsAction {}
class AppSettingsLoadedActions extends AppSettingsAction with BoolAction {
AppSettingsLoadedActions(AppSettings appSettings) : super(appSettings);
}
class UpdateAppSettingsActions extends AppSettingsAction {
UpdateAppSettingsActions(AppSettings appSettings) : super(appSettings);
}
class AppSettingsNotLoadedAction extends BoolAction {}

View File

@ -0,0 +1,16 @@
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
abstract class BoolAction {}
abstract class AppSettingsAction {
final AppSettings appSettings;
AppSettingsAction(this.appSettings);
}
abstract class TimeProgressAction {
final TimeProgress timeProgress;
TimeProgressAction(this.timeProgress);
}

View File

@ -0,0 +1,29 @@
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/redux/actions/redux_actions.dart';
class LoadTimeProgressListAction {}
class TimeProgressListLoadedAction extends BoolAction {
final List<TimeProgress> timeProgressList;
TimeProgressListLoadedAction(this.timeProgressList);
}
class TimeProgressListNotLoadedAction extends BoolAction {}
class AddTimeProgressAction extends TimeProgressAction {
AddTimeProgressAction(TimeProgress timeProgress) : super(timeProgress);
}
class UpdateTimeProgressAction extends TimeProgressAction {
final String id;
UpdateTimeProgressAction(this.id, TimeProgress timeProgress)
: super(timeProgress);
}
class DeleteTimeProgressAction {
final String id;
DeleteTimeProgressAction(this.id);
}

41
lib/redux/app_state.dart Normal file
View File

@ -0,0 +1,41 @@
import 'package:meta/meta.dart';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/utils/constants.dart';
@immutable
class AppState {
final bool hasProgressesLoaded, hasSettingsLoaded;
final List<TimeProgress> timeProgressList;
final AppSettings appSettings;
AppState(
{this.hasProgressesLoaded = false,
this.hasSettingsLoaded = false,
this.timeProgressList = const [],
this.appSettings = defaultAppSettings});
factory AppState.initial() =>
AppState(hasProgressesLoaded: false, appSettings: defaultAppSettings);
AppState copyWith({
bool? hasLoaded,
List<TimeProgress>? timeProgressList,
}) {
return AppState(
hasProgressesLoaded: hasLoaded ?? this.hasProgressesLoaded,
timeProgressList: timeProgressList ?? this.timeProgressList,
);
}
@override
int get hashCode => hasProgressesLoaded.hashCode ^ timeProgressList.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is AppState &&
runtimeType == other.runtimeType &&
hasProgressesLoaded == other.hasProgressesLoaded &&
timeProgressList == other.timeProgressList;
}

View File

@ -0,0 +1,14 @@
import 'package:time_progress_tracker/redux/app_state.dart';
import 'package:time_progress_tracker/redux/reducers/bool_reducers.dart';
import 'package:time_progress_tracker/redux/reducers/model_reducers.dart';
AppState appStateReducer(AppState state, dynamic action) {
return AppState(
hasSettingsLoaded:
hasSettingsLoadedReducer(state.hasSettingsLoaded, action),
hasProgressesLoaded:
hasProgressesLoadedReducer(state.hasProgressesLoaded, action),
timeProgressList: timeProgressListReducer(state.timeProgressList, action),
appSettings: appSettingsReducers(state.appSettings, action),
);
}

View File

@ -0,0 +1,22 @@
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/redux/actions/app_settings_actions.dart';
import 'package:time_progress_tracker/redux/actions/redux_actions.dart';
import 'package:time_progress_tracker/redux/actions/time_progress_actions.dart';
final hasProgressesLoadedReducer = combineReducers<bool>([
TypedReducer<bool, TimeProgressListLoadedAction>(_setTrue),
TypedReducer<bool, TimeProgressListNotLoadedAction>(_setFalse)
]);
final hasSettingsLoadedReducer = combineReducers<bool>([
TypedReducer<bool, AppSettingsLoadedActions>(_setTrue),
TypedReducer<bool, AppSettingsNotLoadedAction>(_setFalse)
]);
bool _setTrue(bool value, BoolAction action) {
return true;
}
bool _setFalse(bool value, BoolAction action) {
return false;
}

View File

@ -0,0 +1,63 @@
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/redux/actions/app_settings_actions.dart';
import 'package:time_progress_tracker/redux/actions/redux_actions.dart';
import 'package:time_progress_tracker/redux/actions/time_progress_actions.dart';
import 'package:time_progress_tracker/utils/constants.dart';
final timeProgressListReducer = combineReducers<List<TimeProgress>>([
TypedReducer<List<TimeProgress>, TimeProgressListLoadedAction>(
_setLoadedTimeProgressList),
TypedReducer<List<TimeProgress>, TimeProgressListNotLoadedAction>(
_setEmptyTimeProgressList),
TypedReducer<List<TimeProgress>, AddTimeProgressAction>(_addTimeProgress),
TypedReducer<List<TimeProgress>, UpdateTimeProgressAction>(
_updateTimeProgress),
TypedReducer<List<TimeProgress>, DeleteTimeProgressAction>(
_deleteTimeProgress),
]);
List<TimeProgress> _setEmptyTimeProgressList(
List<TimeProgress> timeProgressList,
TimeProgressListNotLoadedAction action) {
return [];
}
List<TimeProgress> _setLoadedTimeProgressList(
List<TimeProgress> timeProgressList, TimeProgressListLoadedAction action) {
return action.timeProgressList;
}
List<TimeProgress> _addTimeProgress(
List<TimeProgress> timeProgressList, AddTimeProgressAction action) =>
List.from(timeProgressList)
..add(action.timeProgress)
..toList(growable: false);
List<TimeProgress> _updateTimeProgress(
List<TimeProgress> timeProgressList, UpdateTimeProgressAction action) =>
timeProgressList
.map((timeProgress) =>
timeProgress.id == action.id ? action.timeProgress : timeProgress)
.toList(growable: false);
List<TimeProgress> _deleteTimeProgress(
List<TimeProgress> timeProgressList, DeleteTimeProgressAction action) =>
timeProgressList
.where((timeProgress) => timeProgress.id != action.id)
.toList(growable: false);
final appSettingsReducers = combineReducers<AppSettings>([
TypedReducer<AppSettings, AppSettingsLoadedActions>(_updateAppSettings),
TypedReducer<AppSettings, UpdateAppSettingsActions>(_updateAppSettings),
TypedReducer<AppSettings, AppSettingsNotLoadedAction>(_setDefaultSettings)
]);
AppSettings _setDefaultSettings(
AppSettings appSettings, AppSettingsNotLoadedAction action) =>
defaultAppSettings;
AppSettings _updateAppSettings(
AppSettings appSettings, AppSettingsAction action) =>
action.appSettings;

View File

@ -1,5 +1,8 @@
import 'package:time_progress_tracker/models/app_state.dart'; import 'dart:ui';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/time_progress.dart'; import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/redux/app_state.dart';
List<TimeProgress> timeProgressListSelector(AppState state) => List<TimeProgress> timeProgressListSelector(AppState state) =>
state.timeProgressList; state.timeProgressList;
@ -42,8 +45,16 @@ List<TimeProgress> pastTimeProgressesSelector(AppState state) =>
DateTime.now().millisecondsSinceEpoch) DateTime.now().millisecondsSinceEpoch)
.toList(); .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 TimeProgress tp = state.timeProgressList.firstWhere(
.firstWhere((timeProgress) => timeProgress.id == id, orElse: () => null); (timeProgress) => timeProgress.id == id,
orElse: () => TimeProgress.initialDefault());
return tp != TimeProgress.initialDefault() ? tp : null;
} }
AppSettings appSettingsSelector(AppState state) {
return state.appSettings;
}
Color doneColorSelector(AppState state) => state.appSettings.doneColor;

View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/redux/actions/time_progress_actions.dart';
import 'package:time_progress_tracker/redux/app_state.dart';
import 'package:time_progress_tracker/redux/redux_selectors.dart';
import '../../utils/helper_functions.dart';
class CreateTimeProgressStoreConnector extends StatelessWidget {
final Widget Function(BuildContext, CreateTimeProgressViewModel)
loadedBuilder;
CreateTimeProgressStoreConnector({
required this.loadedBuilder,
});
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, CreateTimeProgressViewModel>(
onInit: loadTimeProgressListIfUnloaded,
converter: (store) => CreateTimeProgressViewModel._create(store),
builder: (context, CreateTimeProgressViewModel vm) {
return loadedBuilder(context, vm);
},
);
}
}
class CreateTimeProgressViewModel {
final TimeProgress defaultProgress;
final void Function(TimeProgress) addTimeProgress;
CreateTimeProgressViewModel(
this.defaultProgress,
this.addTimeProgress,
);
factory CreateTimeProgressViewModel._create(Store<AppState> store) {
AppSettings settings = appSettingsSelector(store.state);
void _addTimeProgress(TimeProgress tp) {
if (TimeProgress.isValid(tp)) store.dispatch(AddTimeProgressAction(tp));
}
return CreateTimeProgressViewModel(
TimeProgress.defaultFromDuration(settings.duration),
_addTimeProgress,
);
}
}

View File

@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/redux/actions/app_settings_actions.dart';
import 'package:time_progress_tracker/redux/app_state.dart';
import 'package:time_progress_tracker/utils/helper_functions.dart';
class SettingsStoreConnector extends StatelessWidget {
final Widget Function(BuildContext, SettingsViewModel) loadedBuilder;
SettingsStoreConnector({
required this.loadedBuilder,
});
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, SettingsViewModel>(
onInit: loadSettingsIfUnloaded,
converter: (store) => SettingsViewModel._create(store),
builder: (context, SettingsViewModel vm) {
if (!vm.hasSettingsLoaded)
return Center(
child: CircularProgressIndicator(),
);
return loadedBuilder(context, vm);
},
);
}
}
class SettingsViewModel {
final AppSettings appSettings;
final bool hasSettingsLoaded;
final void Function(Color) updateDoneColor, updateLeftColor;
final void Function(Duration) updateDuration;
SettingsViewModel(
this.appSettings,
this.hasSettingsLoaded,
this.updateDoneColor,
this.updateLeftColor,
this.updateDuration,
);
factory SettingsViewModel._create(Store<AppState> store) {
AppSettings _appSettings = store.state.appSettings;
void _updateDoneColor(Color dC) => store.dispatch(
UpdateAppSettingsActions(_appSettings.copyWith(doneColor: dC)));
void _updateLeftColor(Color lC) => store.dispatch(
UpdateAppSettingsActions(_appSettings.copyWith(leftColor: lC)));
void _updateDuration(Duration d) => store
.dispatch(UpdateAppSettingsActions(_appSettings.copyWith(duration: d)));
return SettingsViewModel(_appSettings, store.state.hasSettingsLoaded,
_updateDoneColor, _updateLeftColor, _updateDuration);
}
}

View File

@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/redux/app_state.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/utils/helper_functions.dart';
class TimeProgressListStoreConnector extends StatelessWidget {
final Widget Function(BuildContext, TimeProgressListViewModel) loadedBuilder;
TimeProgressListStoreConnector({
required this.loadedBuilder,
});
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, TimeProgressListViewModel>(
onInit: loadTimeProgressListIfUnloaded,
converter: (store) => TimeProgressListViewModel._create(store),
builder: (context, TimeProgressListViewModel vm) {
if (!vm.hasTpListLoaded)
return Center(
child: CircularProgressIndicator(),
);
return loadedBuilder(context, vm);
},
);
}
}
class TimeProgressListViewModel {
final List<TimeProgress> tpList;
final bool hasTpListLoaded;
TimeProgressListViewModel(this.tpList, this.hasTpListLoaded);
factory TimeProgressListViewModel._create(Store<AppState> store) =>
TimeProgressListViewModel(
store.state.timeProgressList, store.state.hasProgressesLoaded);
}

View File

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/redux/actions/time_progress_actions.dart';
import 'package:time_progress_tracker/redux/app_state.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/utils/helper_functions.dart';
class TimeProgressStoreConnector extends StatelessWidget {
final String timeProgressId;
final Widget Function(BuildContext, TimeProgressViewModel) loadedBuilder;
TimeProgressStoreConnector({
required this.timeProgressId,
required this.loadedBuilder,
});
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, TimeProgressViewModel>(
onInit: loadTimeProgressListIfUnloaded,
converter: (store) =>
TimeProgressViewModel._create(store, timeProgressId),
builder: (context, TimeProgressViewModel vm) {
if (!vm.hasTpListLoaded)
return Center(
child: CircularProgressIndicator(),
);
if (vm.tp == null)
return Center(
child: Text("Error Invalid Time Progress"),
);
return loadedBuilder(context, vm);
},
);
}
}
class TimeProgressViewModel {
final TimeProgress? tp;
final bool hasTpListLoaded;
final void Function(TimeProgress) updateTimeProgress;
final void Function() deleteTimeProgress;
TimeProgressViewModel(
this.tp,
this.hasTpListLoaded,
this.updateTimeProgress,
this.deleteTimeProgress,
);
factory TimeProgressViewModel._create(Store<AppState> store, String id) {
void _updateTimeProgress(TimeProgress tp) =>
store.dispatch(UpdateTimeProgressAction(id, tp));
void _deleteTimeProgress() => store.dispatch(DeleteTimeProgressAction(id));
return TimeProgressViewModel(
selectProgressById(store.state.timeProgressList, id),
store.state.hasProgressesLoaded,
_updateTimeProgress,
_deleteTimeProgress,
);
}
}

View File

@ -0,0 +1,70 @@
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/persistence/app_settings.dart';
import 'package:time_progress_tracker/persistence/time_progress.dart';
import 'package:time_progress_tracker/redux/actions/app_settings_actions.dart';
import 'package:time_progress_tracker/redux/actions/time_progress_actions.dart';
import 'package:time_progress_tracker/redux/app_state.dart';
import 'package:time_progress_tracker/redux/redux_selectors.dart';
List<Middleware<AppState>> createStoreMiddleware(
TimeProgressRepository progressRepo, AppSettingsRepository settingsRepo) {
final saveTimeProgressList = _createSaveTimeProgressList(progressRepo);
final loadTimeProgressList = _createLoadTimeProgressList(progressRepo);
final saveSettings = _createSaveAppSettings(settingsRepo);
final loadSettings = _createLoadAppSettings(settingsRepo);
return [
TypedMiddleware<AppState, LoadTimeProgressListAction>(loadTimeProgressList),
TypedMiddleware<AppState, AddTimeProgressAction>(saveTimeProgressList),
TypedMiddleware<AppState, UpdateTimeProgressAction>(saveTimeProgressList),
TypedMiddleware<AppState, DeleteTimeProgressAction>(saveTimeProgressList),
TypedMiddleware<AppState, LoadAppSettingsAction>(loadSettings),
TypedMiddleware<AppState, UpdateAppSettingsActions>(saveSettings)
];
}
Middleware<AppState> _createSaveTimeProgressList(
TimeProgressRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
next(action);
repository.save(
timeProgressListSelector(store.state)
.map<TimeProgressEntity>((timeProgress) => timeProgress.toEntity())
.toList(growable: false),
);
};
}
Middleware<AppState> _createLoadTimeProgressList(
TimeProgressRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
repository.load().then((timeProgresses) {
List<TimeProgress> timeProgressList =
timeProgresses.map<TimeProgress>(TimeProgress.fromEntity).toList();
if (timeProgressList == null) {
timeProgressList = [];
}
store.dispatch(TimeProgressListLoadedAction(
timeProgressList,
));
}).catchError((_) => store.dispatch(TimeProgressListNotLoadedAction()));
};
}
Middleware<AppState> _createSaveAppSettings(AppSettingsRepository repo) =>
(Store<AppState> store, dynamic action, NextDispatcher next) {
next(action);
repo.save(store.state.appSettings.toEntity());
};
Middleware<AppState> _createLoadAppSettings(AppSettingsRepository repo) =>
(Store<AppState> store, dynamic action, NextDispatcher next) {
repo.load().then((appSettings) {
store.dispatch(
AppSettingsLoadedActions(AppSettings.fromEntity(appSettings)));
});
};

View File

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

@ -1,74 +0,0 @@
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/widgets/progress_editor_widget.dart';
class ProgressCreationScreen extends StatefulWidget {
static const routeName = "/create-progress";
static const title = "Create Time Progress";
@override
State<StatefulWidget> createState() {
return _ProgressCreationScreenState();
}
}
class _ProgressCreationScreenState extends State<ProgressCreationScreen> {
TimeProgress timeProgressToCreate =
TimeProgress("", DateTime.now(), DateTime(DateTime.now().year + 1));
bool _isProgressValid = false;
void onTimeProgressChanged(
TimeProgress newTimeProgress, bool isNewProgressValid) {
setState(() {
timeProgressToCreate = newTimeProgress;
_isProgressValid = isNewProgressValid;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(ProgressCreationScreen.title),
),
body: Container(
padding: EdgeInsets.all(12),
child: ProgressEditorWidget(
timeProgress: timeProgressToCreate,
onTimeProgressChanged: onTimeProgressChanged,
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: Row(
children: <Widget>[
Expanded(
child: FloatingActionButton(
heroTag: "createTimeProgressBTN",
child: Icon(Icons.save),
onPressed: _isProgressValid
? () {
StoreProvider.of<AppState>(context).dispatch(
AddTimeProgressAction(timeProgressToCreate));
Navigator.pop(context);
}
: null,
),
),
Expanded(
child: FloatingActionButton(
heroTag: "cancelTimeProgressCreationBTN",
child: Icon(Icons.cancel),
onPressed: () {
Navigator.pop(context);
},
),
)
],
),
);
}
}

View File

@ -1,128 +0,0 @@
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/home_screen.dart';
import 'package:time_progress_tracker/selectors/time_progress_selectors.dart';
import 'package:time_progress_tracker/widgets/detail_screen_floating_action_buttons.dart';
import 'package:time_progress_tracker/widgets/progress_editor_widget.dart';
import 'package:time_progress_tracker/widgets/progress_view_widget.dart';
class ProgressDetailScreenArguments {
final String id;
ProgressDetailScreenArguments(this.id);
}
class ProgressDetailScreen extends StatefulWidget {
static const routeName = "/progress";
static const title = "Progress View";
@override
State<StatefulWidget> createState() {
return _ProgressDetailScreenState();
}
}
class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
bool _editMode = false, _isEditedProgressValid = false;
TimeProgress _editedProgress, _originalProgress;
void _onEditedProgressChanged(
TimeProgress newProgress, bool isNewProgressValid) {
setState(() {
_editedProgress = newProgress;
_isEditedProgressValid = isNewProgressValid;
});
}
void _switchEditMode(bool newMode) {
setState(() {
_editMode = newMode;
});
}
void _cancelEditMode() {
setState(() {
_editMode = false;
_editedProgress = _originalProgress;
});
}
@override
Widget build(BuildContext context) {
final ProgressDetailScreenArguments args =
ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: AppBar(
title: Text(ProgressDetailScreen.title),
),
body: Container(
margin: EdgeInsets.all(8),
child: StoreConnector(
onInit: loadTimeProgressListIfUnloaded,
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(
children: columnChildren,
);
},
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: StoreConnector(
onInit: loadTimeProgressListIfUnloaded,
converter: (store) => timeProgressByIdSelector(store.state, args.id),
builder: (BuildContext context, TimeProgress timeProgress) {
final Store<AppState> store = StoreProvider.of<AppState>(context);
void _saveEditedProgress() {
store
.dispatch(UpdateTimeProgressAction(args.id, _editedProgress));
_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);
}),
);
}
}

View File

@ -6,10 +6,10 @@ class AppYesNoDialog extends StatelessWidget {
final void Function() onYesPressed; final void Function() onYesPressed;
AppYesNoDialog({ AppYesNoDialog({
Key key, Key? key,
@required this.titleText, required this.titleText,
@required this.contentText, required this.contentText,
@required this.onYesPressed, required this.onYesPressed,
}) : super(key: key); }) : super(key: key);
@override @override

View File

@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:time_progress_tracker/utils/helper_functions.dart';
class ColorPickerButton extends StatelessWidget {
final String title, dialogTitle;
final Color selectedColor;
final void Function(Color) onColorPicked;
ColorPickerButton({
required this.title,
required this.dialogTitle,
required this.selectedColor,
required this.onColorPicked,
});
@override
Widget build(BuildContext context) {
ThemeData appTheme = Theme.of(context);
return TextButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(dialogTitle),
content: SingleChildScrollView(
child: BlockPicker(
pickerColor: selectedColor,
onColorChanged: onColorPicked,
),
),
);
},
);
},
child: Text(title),
style: TextButton.styleFrom(
primary: useBrightBackground(selectedColor)
? appTheme.primaryTextTheme.button!.color
: appTheme.textTheme.button!.color,
backgroundColor: selectedColor,
),
);
}
}

View File

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:time_progress_tracker/utils/helper_functions.dart';
class CreateProgressButton extends StatelessWidget {
final String _heroTag = "createTimeProgressBTN";
final void Function() createProgress;
const CreateProgressButton({Key? key, required this.createProgress})
: super(key: key);
@override
Widget build(BuildContext context) {
Widget _renderCupertino() {
return PlatformButton(
padding: EdgeInsets.all(4),
child: Icon(
Icons.save,
color: Colors.white,
),
onPressed: () => createProgress(),
);
}
Widget _renderMaterial() {
return FloatingActionButton(
heroTag: _heroTag,
child: Icon(Icons.save),
onPressed: () => createProgress(),
);
}
return useCupertino() ? _renderCupertino() : _renderMaterial();
}
}

View File

@ -0,0 +1,36 @@
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();
void _onButtonPressed(BuildContext context) async {
onDatePicked((await showDatePicker(
context: context,
initialDate: pickedDate,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
))!);
}
@override
Widget build(BuildContext context) {
ThemeData appTheme = Theme.of(context);
return TextButton(
onPressed: () => _onButtonPressed(context),
child: Text(
"$leadingString ${pickedDate.toLocal().toString().split(" ")[0]}"),
style: TextButton.styleFrom(
primary: appTheme.primaryTextTheme.button!.color,
backgroundColor: appTheme.accentColor,
),
);
}
}

View File

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:time_progress_tracker/ui/buttons/platform_action_button.dart';
class DetailScreenLeadingButton extends StatelessWidget {
final bool isEditMode, isEditedTpValid;
final void Function() saveTp, editTp;
const DetailScreenLeadingButton({
Key? key,
required this.isEditMode,
required this.isEditedTpValid,
required this.saveTp,
required this.editTp,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final ThemeData materialTheme = Theme.of(context);
if (isEditMode)
return PlatformActionButton(
heroTag: "saveEditedTimeProgressBTN",
icon: Icons.save,
materialBackground: Colors.green,
onBtnPressed: isEditedTpValid ? saveTp : null);
return PlatformActionButton(
heroTag: "editTimeProgressBTN",
icon: Icons.edit,
materialBackground: materialTheme.accentColor,
onBtnPressed: editTp,
);
}
}

View File

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:time_progress_tracker/ui/buttons/platform_action_button.dart';
class DetailScreenTrailingButton extends StatelessWidget {
final bool isEditMode;
final void Function() cancelEditTp, deleteTp;
const DetailScreenTrailingButton({
Key? key,
required this.isEditMode,
required this.cancelEditTp,
required this.deleteTp,
}) : super(key: key);
@override
Widget build(BuildContext context) {
if (isEditMode)
return PlatformActionButton(
heroTag: "cancelEditTimeProgressBTN",
icon: Icons.cancel,
materialBackground: Colors.red,
onBtnPressed: cancelEditTp);
return PlatformActionButton(
heroTag: "deleteTimeProgressBTN",
icon: Icons.delete,
materialBackground: Colors.red,
onBtnPressed: deleteTp,
);
}
}

View File

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:time_progress_tracker/utils/helper_functions.dart';
class PlatformActionButton extends StatelessWidget {
final String heroTag;
final IconData icon;
final Color? materialBackground;
final void Function()? onBtnPressed;
const PlatformActionButton({
Key? key,
required this.heroTag,
required this.icon,
this.materialBackground,
required this.onBtnPressed,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Widget _renderCupertino() {
return PlatformButton(
padding: EdgeInsets.all(4),
child: Icon(
icon,
color: Colors.white,
),
onPressed: onBtnPressed,
);
}
Widget _renderMaterial() {
return FloatingActionButton(
heroTag: heroTag,
child: Icon(icon),
backgroundColor: materialBackground,
onPressed: onBtnPressed,
);
}
return useCupertino() ? _renderCupertino() : _renderMaterial();
}
}

View File

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:flutter_picker/flutter_picker.dart';
class SelectDurationBtn extends StatelessWidget {
final Duration duration;
final void Function(Duration) updateDuration;
SelectDurationBtn({
required this.duration,
required this.updateDuration,
});
void _onPickerConfirm(Picker picker, List<int> values) {
int years = values[0], months = values[1], days = values[2];
days = (years * 365) + (months * 31) + days;
Duration newDuration = Duration(days: days);
updateDuration(newDuration);
}
void _onButtonPressed(BuildContext context, ThemeData appTheme) => Picker(
adapter: NumberPickerAdapter(data: [
const NumberPickerColumn(begin: 0, end: 999, suffix: Text(" Y")),
const NumberPickerColumn(begin: 0, end: 11, suffix: Text(" M")),
const NumberPickerColumn(begin: 0, end: 31, suffix: Text(" D")),
]),
hideHeader: false,
title: const Text("Default Duration"),
selectedTextStyle: TextStyle(color: appTheme.accentColor),
onConfirm: _onPickerConfirm)
.showModal(context);
@override
Widget build(BuildContext context) {
ThemeData appTheme = Theme.of(context);
int years = duration.inDays ~/ 365;
int months = (duration.inDays - (365 * years)) ~/ 30;
int days = duration.inDays - (365 * years) - (30 * months);
return TextButton(
onPressed: () => _onButtonPressed(context, appTheme),
child: Text("$years Years $months Months $days Days"),
style: TextButton.styleFrom(
primary: appTheme.primaryTextTheme.button!.color,
backgroundColor: appTheme.accentColor,
));
}
}

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:time_progress_tracker/models/time_progress.dart'; import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/widgets/app_yes_no_dialog_widget.dart'; import 'package:time_progress_tracker/ui/app_yes_no_dialog_widget.dart';
class DetailScreenFloatingActionButtons extends StatelessWidget { class DetailScreenFloatingActionButtons extends StatelessWidget {
final bool editMode, isEditedProgressValid; final bool editMode, isEditedProgressValid;
@ -11,14 +11,14 @@ class DetailScreenFloatingActionButtons extends StatelessWidget {
onDeleteProgress; onDeleteProgress;
DetailScreenFloatingActionButtons({ DetailScreenFloatingActionButtons({
@required this.editMode, required this.editMode,
@required this.originalProgress, required this.originalProgress,
@required this.editedProgress, required this.editedProgress,
@required this.isEditedProgressValid, required this.isEditedProgressValid,
@required this.onEditProgress, required this.onEditProgress,
@required this.onSaveEditedProgress, required this.onSaveEditedProgress,
@required this.onCancelEditProgress, required this.onCancelEditProgress,
@required this.onDeleteProgress, required this.onDeleteProgress,
}); });
@override @override

View File

@ -1,14 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:time_progress_tracker/models/time_progress.dart'; import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/widgets/date_picker_btn.dart'; import 'package:time_progress_tracker/ui/buttons/date_picker_btn.dart';
class ProgressEditorWidget extends StatefulWidget { class ProgressEditorWidget extends StatefulWidget {
final TimeProgress timeProgress; final TimeProgress timeProgress;
final Function(TimeProgress, bool) onTimeProgressChanged; final Function(TimeProgress, bool) onTimeProgressChanged;
ProgressEditorWidget({ ProgressEditorWidget({
@required this.timeProgress, required this.timeProgress,
@required this.onTimeProgressChanged, required this.onTimeProgressChanged,
}); });
@override @override
@ -64,14 +65,25 @@ class _ProgressEditorWidgetState extends State<ProgressEditorWidget> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Widget> columnChildren = [ List<Widget> columnChildren = [
Expanded( Expanded(
child: TextField( child: Center(
controller: _nameTextController, child: PlatformTextField(
decoration: InputDecoration( controller: _nameTextController,
border: OutlineInputBorder(), material: (context, platform) => MaterialTextFieldData(
labelText: "Progress Name", decoration: InputDecoration(
errorText: _validName border: OutlineInputBorder(),
? null labelText: "Progress Name",
: "The Name need to have at least 3 and at max 20 symbols.", errorText: _validName
? null
: "The Name need to have at least 3 and at max 20 symbols.",
),
),
cupertino: (context, platform) => CupertinoTextFieldData(
placeholder: "Progress Name",
decoration: BoxDecoration(
border: Border.all(width: 2, color: Colors.grey),
borderRadius: BorderRadius.circular(32),
)
),
), ),
), ),
), ),

View File

@ -0,0 +1,122 @@
import 'dart:io' show Platform;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/ui/screens/progress_detail_screen.dart';
import 'package:time_progress_tracker/utils/theme_utils.dart';
class ProgressListTileStrings {
static String percentString(TimeProgress tp) =>
"${(tp.percentDone() * 100).floorToDouble()} %";
static String startsInDaysString(TimeProgress tp) =>
"Starts in ${tp.daysTillStart()} Days.";
static String endedDaysAgoString(TimeProgress tp) =>
"Ended ${tp.daysSinceEnd()} Days ago.";
}
class ProgressListItem extends StatelessWidget {
final TimeProgress timeProgress;
final Color doneColor, leftColor;
ProgressListItem({
required this.timeProgress,
required this.doneColor,
required this.leftColor,
});
@override
Widget build(BuildContext context) {
void _onTileTap() => Navigator.push(
context,
platformPageRoute(
context: context,
builder: (context) => ProgressDetailScreen(
tpId: timeProgress.id,
)),
);
Widget _renderTitle(bool material) {
Text name = Text(
timeProgress.name,
style: material ? null : cupertinoCardTitleStyle,
textAlign: material ? null : TextAlign.left,
);
Text duration = Text(
"${timeProgress.allDays()} Days",
style: material ? null : cupertinoCardSubtitleStyle,
textAlign: material ? null : TextAlign.left,
);
Row title = Row(
children: [name, Spacer(), duration],
);
if (!material)
return Padding(
padding: EdgeInsets.only(bottom: 16, right: 5),
child: title,
);
return title;
}
Widget _renderSubtitle(bool material) {
if (!timeProgress.hasStarted())
return PlatformText(
ProgressListTileStrings.startsInDaysString(timeProgress));
if (timeProgress.hasEnded())
return PlatformText(
ProgressListTileStrings.endedDaysAgoString(timeProgress));
LinearPercentIndicator percentIndicator = LinearPercentIndicator(
center:
PlatformText(ProgressListTileStrings.percentString(timeProgress)),
percent: timeProgress.percentDone(),
progressColor: doneColor,
backgroundColor: leftColor,
lineHeight: 20,
);
if (!material)
return Padding(
padding: EdgeInsets.only(
bottom: 5,
right: 5,
),
child: percentIndicator,
);
return percentIndicator;
}
Widget _renderCupertino() {
CupertinoThemeData theme = CupertinoTheme.of(context);
return CupertinoButton(
child: Container(
decoration: BoxDecoration(
color: theme.primaryColor,
borderRadius: BorderRadius.circular(12),
),
padding: EdgeInsets.fromLTRB(15, 15, 5, 5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_renderTitle(false),
_renderSubtitle(false),
],
),
),
onPressed: _onTileTap);
}
Widget _renderMaterial() {
return ListTile(
title: _renderTitle(true),
subtitle: _renderSubtitle(true),
onTap: _onTileTap,
);
}
if (Platform.isIOS) return _renderCupertino();
return _renderMaterial();
}
}

View File

@ -0,0 +1,40 @@
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/ui/progress/progress_list_item.dart';
class ProgressListView extends StatelessWidget {
final List<TimeProgress> timeProgressList;
final Color doneColor, leftColor;
ProgressListView({
required this.timeProgressList,
required this.doneColor,
required this.leftColor,
});
Widget _renderListTile(TimeProgress tp) {
ProgressListItem listTile = ProgressListItem(
timeProgress: tp, doneColor: doneColor, leftColor: leftColor);
if (Platform.isIOS) return listTile;
return Card(
child: listTile,
);
}
List<Widget> _renderListViewChildren() {
return timeProgressList
.map((e) => _renderListTile(e))
.toList(growable: false);
}
@override
Widget build(BuildContext context) {
return ListView(
padding: EdgeInsets.all(8),
children: _renderListViewChildren(),
);
}
}

View File

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

View File

@ -0,0 +1,32 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:time_progress_tracker/redux/store_connectors/settings_store_connector.dart';
import 'package:time_progress_tracker/redux/store_connectors/time_progress_list_store_connector.dart';
import 'package:time_progress_tracker/utils/helper_functions.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/ui/progress/progress_list_view.dart';
class ActiveTimeProgressesScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SettingsStoreConnector(loadedBuilder: (context, settingsVm) {
return TimeProgressListStoreConnector(loadedBuilder: (context, tpListVm) {
List<TimeProgress> activeTpList =
selectActiveProgresses(tpListVm.tpList);
if (activeTpList.length < 1)
return Container(
padding: EdgeInsets.all(16),
child: Center(
child: PlatformText(
"You don't have any active time progress, that are tracked."),
),
);
return ProgressListView(
timeProgressList: activeTpList,
doneColor: settingsVm.appSettings.doneColor,
leftColor: settingsVm.appSettings.leftColor,
);
});
});
}
}

View File

@ -0,0 +1,104 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:time_progress_tracker/ui/buttons/platform_action_button.dart';
import 'package:time_progress_tracker/ui/screens/active_time_progresses_screen.dart';
import 'package:time_progress_tracker/ui/screens/inactive_time_progresses_screen.dart';
import 'package:time_progress_tracker/ui/screens/progress_creation_screen.dart';
import 'package:time_progress_tracker/ui/screens/settings_screen.dart';
import 'package:time_progress_tracker/utils/theme_utils.dart';
import 'package:time_progress_tracker/utils/constants.dart';
class DashboardScreen extends StatefulWidget {
static const routeName = "/dashboard";
@override
State<StatefulWidget> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
int _tabSelectedIndex = 0;
String title = txtActiveProgressesScreen;
Widget _renderTabScreen(int tabIndex) {
switch (tabIndex) {
case 1:
return InactiveTimeProgressesScreen();
case 2:
return SettingsScreen();
default:
return ActiveTimeProgressesScreen();
}
}
String getScreenTitle(int tabIndex) {
switch (tabIndex) {
case 1:
return txtInactiveProgressesScreen;
case 2:
return txtSettingsScreen;
default:
return txtActiveProgressesScreen;
}
}
@override
Widget build(BuildContext context) {
PlatformActionButton? _renderCreateProgressBtn() => _tabSelectedIndex == 2
? null
: PlatformActionButton(
heroTag: "goToCreateTimeProgressBTN",
icon: Icons.add,
onBtnPressed: () => Navigator.push(
context,
platformPageRoute(
context: context,
builder: (context) => ProgressCreationScreen(),
),
),
);
return PlatformScaffold(
appBar: PlatformAppBar(
title: Text(
title,
style: toolbarTextStyle,
),
cupertino: (_, __) => CupertinoNavigationBarData(
transitionBetweenRoutes: false,
leading: _renderCreateProgressBtn(),
),
),
material: (_, __) => MaterialScaffoldData(
floatingActionButton: _renderCreateProgressBtn(),
),
body: _renderTabScreen(_tabSelectedIndex),
bottomNavBar: PlatformNavBar(
currentIndex: _tabSelectedIndex,
itemChanged: (index) {
setState(() {
_tabSelectedIndex = index;
title = getScreenTitle(index);
});
},
backgroundColor: bottomTabsBackground,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.alarm, color: Colors.grey),
label: txtActiveProgressesScreen,
activeIcon: Icon(Icons.alarm, color: Colors.white),
),
BottomNavigationBarItem(
icon: Icon(Icons.alarm_off, color: Colors.grey),
label: txtInactiveProgressesScreen,
activeIcon: Icon(Icons.alarm_off, color: Colors.white),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings, color: Colors.grey),
label: txtSettingsScreen,
activeIcon: Icon(Icons.settings, color: Colors.white),
)
]),
);
}
}

View File

@ -0,0 +1,32 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:time_progress_tracker/redux/store_connectors/settings_store_connector.dart';
import 'package:time_progress_tracker/redux/store_connectors/time_progress_list_store_connector.dart';
import 'package:time_progress_tracker/utils/helper_functions.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/ui/progress/progress_list_view.dart';
class InactiveTimeProgressesScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SettingsStoreConnector(loadedBuilder: (context, settingsVm) {
return TimeProgressListStoreConnector(loadedBuilder: (context, tpListVm) {
List<TimeProgress> activeTpList =
selectInactiveProgresses(tpListVm.tpList);
if (activeTpList.length < 1)
return Container(
padding: EdgeInsets.all(16),
child: Center(
child: PlatformText(
"You don't have any inactive time progress, that are tracked."),
),
);
return ProgressListView(
timeProgressList: activeTpList,
doneColor: settingsVm.appSettings.doneColor,
leftColor: settingsVm.appSettings.leftColor,
);
});
});
}
}

View File

@ -0,0 +1,200 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/redux/actions/time_progress_actions.dart';
import 'package:time_progress_tracker/redux/app_state.dart';
import 'package:time_progress_tracker/redux/redux_selectors.dart';
import 'package:time_progress_tracker/redux/store_connectors/create_time_progress_store_connector.dart';
import 'package:time_progress_tracker/ui/buttons/create_progress_button.dart';
import 'package:time_progress_tracker/ui/buttons/platform_action_button.dart';
import 'package:time_progress_tracker/ui/progress/progress_editor_widget.dart';
import 'package:time_progress_tracker/utils/theme_utils.dart';
import 'package:time_progress_tracker/utils/helper_functions.dart';
class ProgressCreationScreen extends StatefulWidget {
static const routeName = "/create-progress";
static const title = "Create Time Progress";
@override
State<StatefulWidget> createState() {
return _ProgressCreationScreenState();
}
}
class _ProgressCreationScreenState extends State<ProgressCreationScreen> {
TimeProgress timeProgressToCreate = TimeProgress.initialDefault();
bool _isProgressValid = false;
void initTimeProgress(TimeProgress timeProgress) {
if (timeProgressToCreate == TimeProgress.initialDefault())
setState(() {
timeProgressToCreate = timeProgress;
});
}
void onTimeProgressChanged(
TimeProgress newTimeProgress, bool isNewProgressValid) {
setState(() {
timeProgressToCreate = newTimeProgress;
_isProgressValid = isNewProgressValid;
});
}
@override
Widget build(BuildContext context) {
void _onCreateTimeProgress(CreateTimeProgressViewModel vm) {
if (!_isProgressValid) return null;
vm.addTimeProgress(timeProgressToCreate);
Navigator.pop(context);
}
initTimeProgress(TimeProgress.defaultFromDuration(
StoreProvider.of<AppState>(context).state.appSettings.duration));
Widget _createActionButton = CreateTimeProgressStoreConnector(
loadedBuilder: (context, CreateTimeProgressViewModel vm) =>
PlatformActionButton(
heroTag: "createTimeProgressBTN",
icon: Icons.save,
onBtnPressed: () => _onCreateTimeProgress(vm)),
);
return PlatformScaffold(
appBar: PlatformAppBar(
title: Text(
ProgressCreationScreen.title,
style: toolbarTextStyle,
),
cupertino: (_, __) => CupertinoNavigationBarData(
transitionBetweenRoutes: false,
trailing: _createActionButton,
),
),
material: (_, __) => MaterialScaffoldData(
floatingActionButton: Row(
children: [
Expanded(
child: _createActionButton,
)
],
),
),
body: ProgressEditorWidget(
timeProgress: timeProgressToCreate,
onTimeProgressChanged: onTimeProgressChanged,
),
);
return CreateTimeProgressStoreConnector(
loadedBuilder: (context, CreateTimeProgressViewModel vm) {
initTimeProgress(vm.defaultProgress);
return PlatformScaffold(
appBar: PlatformAppBar(
title: Text(
ProgressCreationScreen.title,
style: toolbarTextStyle,
),
cupertino: (_, __) => CupertinoNavigationBarData(
transitionBetweenRoutes: false,
trailing: CreateProgressButton(
createProgress: () => _onCreateTimeProgress(vm),
),
),
),
material: (_, __) => MaterialScaffoldData(
floatingActionButton: Row(
children: [
Expanded(
child: CreateProgressButton(
createProgress: () => _onCreateTimeProgress(vm),
),
)
],
),
),
body: ProgressEditorWidget(
timeProgress: timeProgressToCreate,
onTimeProgressChanged: onTimeProgressChanged,
),
);
},
);
return Scaffold(
appBar: AppBar(
title: Text(ProgressCreationScreen.title),
),
body: Container(
padding: EdgeInsets.all(12),
child: StoreConnector<AppState, _ViewModel>(
onInit: loadSettingsIfUnloaded,
converter: (store) => _ViewModel.create(store),
builder: (context, _ViewModel viewModel) {
initTimeProgress(viewModel.defaultDurationProgress);
return ProgressEditorWidget(
timeProgress: timeProgressToCreate,
onTimeProgressChanged: onTimeProgressChanged,
);
}),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: Row(
children: <Widget>[
Expanded(
child: StoreConnector<AppState, _ViewModel>(
onInit: loadSettingsIfUnloaded,
converter: (store) => _ViewModel.create(store),
builder: (context, _ViewModel vm) => FloatingActionButton(
heroTag: "createTimeProgressBTN",
child: Icon(Icons.save),
onPressed: _isProgressValid
? () {
vm.onAddTimeProgress(timeProgressToCreate);
Navigator.pop(context);
}
: null,
),
),
),
Expanded(
child: FloatingActionButton(
heroTag: "cancelTimeProgressCreationBTN",
child: Icon(Icons.cancel),
onPressed: () {
Navigator.pop(context);
},
),
)
],
),
);
}
}
class _ViewModel {
final TimeProgress defaultDurationProgress;
final void Function(TimeProgress) onAddTimeProgress;
_ViewModel({
required this.defaultDurationProgress,
required this.onAddTimeProgress,
});
factory _ViewModel.create(Store<AppState> store) {
AppSettings settings = appSettingsSelector(store.state);
_onAddTimeProgress(TimeProgress tp) {
if (TimeProgress.isValid(tp)) store.dispatch(AddTimeProgressAction(tp));
}
return _ViewModel(
defaultDurationProgress:
TimeProgress.defaultFromDuration(settings.duration),
onAddTimeProgress: _onAddTimeProgress,
);
}
}

View File

@ -0,0 +1,152 @@
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/redux/store_connectors/settings_store_connector.dart';
import 'package:time_progress_tracker/redux/store_connectors/time_progress_store_connector.dart';
import 'package:time_progress_tracker/ui/buttons/detail_screen_leading_button.dart';
import 'package:time_progress_tracker/ui/buttons/detail_screen_trailing_button.dart';
import 'package:time_progress_tracker/ui/progress/progress_editor_widget.dart';
import 'package:time_progress_tracker/ui/progress/progress_view_widget.dart';
import 'package:time_progress_tracker/utils/theme_utils.dart';
class ProgressDetailScreen extends StatefulWidget {
static const title = "Progress View";
final String tpId;
const ProgressDetailScreen({Key? key, required this.tpId}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _ProgressDetailScreenState();
}
}
class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
bool _editMode = false, _isEditedProgressValid = false;
TimeProgress _editedProgress = TimeProgress.initialDefault(),
_originalProgress = TimeProgress.initialDefault();
void _initEditedProgress(TimeProgress tp) {
if (_editedProgress == TimeProgress.initialDefault()) {
_editedProgress = tp;
_originalProgress = tp;
}
}
void _onEditedProgressChanged(
TimeProgress newProgress, bool isNewProgressValid) {
setState(() {
_editedProgress = newProgress;
_isEditedProgressValid = isNewProgressValid;
});
}
void _switchEditMode(bool newMode) {
setState(() {
_editMode = newMode;
});
}
void _cancelEditMode() {
setState(() {
_editMode = false;
_editedProgress = _originalProgress;
});
}
List<Widget> _renderColumnChildren(
SettingsViewModel settingsVm, TimeProgressViewModel tpVm) {
TimeProgress tp = tpVm.tp ?? TimeProgress.initialDefault();
List<Widget> columnChildren = [
Expanded(
child: ProgressViewWidget(
timeProgress: _editMode ? _editedProgress : tp,
doneColor: settingsVm.appSettings.doneColor,
leftColor: settingsVm.appSettings.leftColor,
))
];
if (_editMode)
columnChildren.add(Expanded(
child: ProgressEditorWidget(
timeProgress: _editedProgress,
onTimeProgressChanged: _onEditedProgressChanged,
)));
return columnChildren;
}
@override
Widget build(BuildContext context) {
TimeProgressStoreConnector leadingActionButton = TimeProgressStoreConnector(
timeProgressId: widget.tpId,
loadedBuilder: (context, tpVm) {
void _saveEditedTp() {
tpVm.updateTimeProgress(_editedProgress);
_switchEditMode(false);
}
return DetailScreenLeadingButton(
isEditMode: _editMode,
isEditedTpValid: _isEditedProgressValid,
saveTp: _saveEditedTp,
editTp: () => _switchEditMode(true),
);
},
);
TimeProgressStoreConnector trailingActionButton =
TimeProgressStoreConnector(
timeProgressId: widget.tpId,
loadedBuilder: (context, tpVm) {
void _deleteTp() {
tpVm.deleteTimeProgress();
Navigator.popUntil(context, (route) => route.isFirst);
}
return DetailScreenTrailingButton(
isEditMode: _editMode,
cancelEditTp: _cancelEditMode,
deleteTp: _deleteTp,
);
},
);
return PlatformScaffold(
appBar: PlatformAppBar(
title: Text(
ProgressDetailScreen.title,
style: toolbarTextStyle,
),
cupertino: (_, __) => CupertinoNavigationBarData(
transitionBetweenRoutes: false,
leading: leadingActionButton,
trailing: trailingActionButton,
),
),
material: (_, __) => MaterialScaffoldData(
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: Row(
children: [
Expanded(child: leadingActionButton),
Expanded(child: trailingActionButton),
],
),
),
body: SettingsStoreConnector(
loadedBuilder: (context, settingsVm) {
return TimeProgressStoreConnector(
timeProgressId: widget.tpId,
loadedBuilder: (context, tpVm) {
_initEditedProgress(tpVm.tp!);
return Container(
margin: EdgeInsets.all(8),
child: Column(
children: _renderColumnChildren(settingsVm, tpVm),
));
},
);
},
),
);
}
}

View File

@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:time_progress_tracker/app.dart';
import 'package:time_progress_tracker/redux/store_connectors/settings_store_connector.dart';
import 'package:time_progress_tracker/ui/buttons/color_picker_btn.dart';
import 'package:time_progress_tracker/ui/settings/duration_settings_widget.dart';
class SettingsScreen extends StatelessWidget {
Widget _renderColorSettings(
BuildContext context, SettingsViewModel settingsVm) {
ThemeData appTheme = Theme.of(context);
return Expanded(
child: Column(
children: [
Expanded(
child: PlatformText(
"Color Settings",
style: appTheme.primaryTextTheme.caption,
)),
Row(
children: [
Expanded(
child: Padding(
padding: EdgeInsets.only(right: 5),
child: ColorPickerButton(
title: "Done Color",
dialogTitle: "Select Done Color",
selectedColor: settingsVm.appSettings.doneColor,
onColorPicked: settingsVm.updateDoneColor,
),
),
),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 5),
child: ColorPickerButton(
title: "Left Color",
dialogTitle: "Select Left Color",
selectedColor: settingsVm.appSettings.leftColor,
onColorPicked: settingsVm.updateLeftColor,
),
),
)
],
)
],
),
);
}
@override
Widget build(BuildContext context) {
return SettingsStoreConnector(loadedBuilder: (context, settingsVm) {
return Container(
padding: EdgeInsets.all(16),
child: Center(
child: Column(
children: [
Expanded(
child: _renderColorSettings(context, settingsVm),
),
Expanded(
child: DurationSettingsWidget(
duration: settingsVm.appSettings.duration,
updateDuration: settingsVm.updateDuration,
),
),
Spacer(),
Expanded(
child: PlatformButton(
onPressed: () {
showAboutDialog(
context: context,
applicationName: TimeProgressTrackerApp.name,
applicationVersion: "Beta",
applicationLegalese:
'\u00a9Andreas Fahrecker 2020-2021');
},
child: Text("About"),
),
),
],
),
),
);
});
}
}

View File

@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/ui/buttons/color_picker_btn.dart';
class ColorSettingsWidget extends StatelessWidget {
final Color doneColor, leftColor;
final void Function(Color) updateDoneColor, updateLeftColor;
ColorSettingsWidget({
required this.doneColor,
required this.leftColor,
required this.updateDoneColor,
required this.updateLeftColor,
});
@override
Widget build(BuildContext context) {
ThemeData appTheme = Theme.of(context);
return Column(
children: [
Expanded(
child: Text(
"Color Settings",
style: appTheme.textTheme.headline6,
),
),
Row(
children: [
Expanded(
child: Padding(
padding: EdgeInsets.only(right: 5),
child: ColorPickerButton(
title: "Done Color",
dialogTitle: "Select Done Color",
selectedColor: doneColor,
onColorPicked: updateDoneColor,
),
),
),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 5),
child: ColorPickerButton(
title: "Left Color",
dialogTitle: "Select Left Color",
selectedColor: leftColor,
onColorPicked: updateLeftColor,
),
),
),
],
)
],
);
}
}

View File

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/ui/buttons/select_duration_btn.dart';
class DurationSettingsWidget extends StatelessWidget {
final Duration duration;
final void Function(Duration) updateDuration;
DurationSettingsWidget({
required this.duration,
required this.updateDuration,
});
@override
Widget build(BuildContext context) {
ThemeData appTheme = Theme.of(context);
int years = duration.inDays ~/ 365;
int months = (duration.inDays - (365 * years)) ~/ 30;
int days = duration.inDays - (365 * years) - (30 * months);
return Column(
children: [
Expanded(
child: Text(
"Duration Settings",
style: appTheme.textTheme.headline6,
),
),
Row(
children: [
Expanded(
child: SelectDurationBtn(
duration: duration,
updateDuration: updateDuration,
),
)
],
)
],
);
}
}

11
lib/utils/constants.dart Normal file
View File

@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/models/app_settings.dart';
const txtActiveProgressesScreen = "Active Progresses";
const txtInactiveProgressesScreen = "Inactive Progresses";
const txtSettingsScreen = "Settings";
const defaultAppSettings = AppSettings(
doneColor: Colors.green,
leftColor: Colors.red,
duration: Duration(days: 365),
);

View File

@ -0,0 +1,37 @@
import 'dart:io' show Platform;
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/redux/actions/app_settings_actions.dart';
import 'package:time_progress_tracker/redux/actions/time_progress_actions.dart';
import 'package:time_progress_tracker/redux/app_state.dart';
void loadTimeProgressListIfUnloaded(Store<AppState> store) {
if (!store.state.hasProgressesLoaded)
store.dispatch(LoadTimeProgressListAction());
}
void loadSettingsIfUnloaded(Store<AppState> store) {
if (!store.state.hasSettingsLoaded) store.dispatch(LoadAppSettingsAction());
}
TimeProgress? selectProgressById(List<TimeProgress> tpList, String id) =>
tpList.firstWhere((tp) => tp.id == id, orElse: null);
List<TimeProgress> selectActiveProgresses(List<TimeProgress> tpList) =>
tpList.where((tp) => tp.hasStarted() && !tp.hasEnded()).toList();
List<TimeProgress> selectInactiveProgresses(List<TimeProgress> tpList) =>
tpList.where((tp) => !tp.hasStarted() || tp.hasEnded()).toList();
bool useBrightBackground(Color bC) {
double yiq = ((bC.red * 299) + (bC.green * 587) + (bC.blue * 114)) / 1000;
return yiq >= 186 || (bC.red == 0 && bC.green == 0 && bC.blue == 0);
}
bool useCupertino() {
if (kIsWeb) return false;
return Platform.isIOS;
}

View File

@ -0,0 +1,24 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
final ThemeData materialThemeData = ThemeData(
primarySwatch: Colors.indigo,
scaffoldBackgroundColor: Colors.white,
accentColor: Colors.indigo,
appBarTheme: AppBarTheme(color: Colors.indigo.shade600),
primaryColor: Colors.indigo,
secondaryHeaderColor: Colors.indigo,
canvasColor: Colors.indigo,
backgroundColor: Colors.red,
);
final CupertinoThemeData cupertinoThemeData = CupertinoThemeData(
primaryColor: Colors.indigo,
barBackgroundColor: Colors.indigo,
scaffoldBackgroundColor: Colors.white,
);
final toolbarTextStyle = TextStyle(color: Colors.white, fontSize: 16);
final cupertinoCardTitleStyle =
TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.w600);
final cupertinoCardSubtitleStyle =
TextStyle(color: Colors.grey, fontSize: 18, fontWeight: FontWeight.w400);
final bottomTabsBackground = Colors.indigoAccent;

Some files were not shown because too many files have changed in this diff Show More