Compare commits

..

8 Commits

Author SHA1 Message Date
3940184975 Fix Displaying Bug 2024-05-14 20:58:22 +02:00
eee1d12851 Fix Version Name for new Release 2024-03-15 23:05:33 +01:00
e11a73d37e Fixed Android Label 2024-03-15 23:02:13 +01:00
ead31e12c0 Fixed Remaining Migration Errors 2024-03-15 22:47:43 +01:00
421d19f91f Fixed Remaining Problems 2024-03-15 21:09:16 +01:00
3085a295e5 Fixed Simple Problem, that occured after migrating to new version.
Still WIP need to fix more Problems
2024-03-15 07:05:10 +01:00
c12ba48e15 Used Flutter Migrate tool and fixed flutter_picker in pubspec.yaml 2024-03-15 04:34:08 +01:00
Andreas Fahrecker
d2b4b6ee71 Created Project readme
Signed-off-by: Andreas Fahrecker <AndreasFahrecker@gmail.com>
2021-03-12 00:29:58 +01:00
89 changed files with 1620 additions and 1822 deletions

104
.gitignore vendored
View File

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

View File

@ -4,7 +4,42 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: fba99f6cf9a14512e461e3122c8ddfaa25394e89 revision: "ba393198430278b6595976de84fe170f553cc728"
channel: stable channel: "stable"
project_type: app project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: android
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: ios
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: linux
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: macos
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: web
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: windows
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@ -9,13 +9,17 @@ including their current percentages.
## Current State of the repo. ## Current State of the repo.
Now the repo is mostly cleaned up. Currently, the code in this branch is pretty messed up. Since I wrote that application from a
prototype which, was developed as my first flutter project and in a very short time.
Since, then most of the work that went into this project was cleanup work.
My own model classes are located in lib/models. Currently I am working on a clean codebase in the feature/platform-widget branch.
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. At this state, the base screens of the app are located in lib/screens,
The Flutter UI widgets are located in lib/ui. all other ui widgets are located in li/widgets,
Other stuff is in lib/utils or directly in lib. the model classes are located in lib/model,
files related to persisting the data are in lib/persistence,
and redux related files are spread over lib/actions lib/middleware lib/reducers and lib/reducers.
- [Google Play](https://play.google.com/store/apps/details?id=com.fahrecker.time_progress_calculator) - [Google Play](https://play.google.com/store/apps/details?id=com.fahrecker.time_progress_calculator)

28
analysis_options.yaml Normal file
View File

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

2
android/.gitignore vendored
View File

@ -9,3 +9,5 @@ GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore. # Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties key.properties
**/*.keystore
**/*.jks

View File

@ -1,3 +1,9 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties() def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties') def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) { if (localPropertiesFile.exists()) {
@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
} }
} }
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) { if (flutterVersionCode == null) {
flutterVersionCode = '1' flutterVersionCode = '1'
@ -21,10 +22,6 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0' flutterVersionName = '1.0'
} }
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties() def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties') def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) { if (keystorePropertiesFile.exists()) {
@ -32,21 +29,30 @@ if (keystorePropertiesFile.exists()) {
} }
android { android {
compileSdkVersion 28 namespace "com.fahrecker.time_progress_calculator"
compileSdk flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
} }
lintOptions {
disable 'InvalidPackage'
}
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.fahrecker.time_progress_calculator" applicationId "com.fahrecker.time_progress_calculator"
minSdkVersion 16 // You can update the following values to match your application needs.
targetSdkVersion 29 // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }
@ -71,6 +77,4 @@ flutter {
source '../..' source '../..'
} }
dependencies { dependencies {}
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

View File

@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.fahrecker.time_progress_calculator"> package="com.fahrecker.time_progress_calculator">
<!-- Flutter needs it to communicate with the running application <!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>

View File

@ -1,16 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.fahrecker.time_progress_calculator"> package="com.fahrecker.time_progress_calculator">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application <application
android:name="io.flutter.app.FlutterApplication"
android:label="Time Progress Tracker" android:label="Time Progress Tracker"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
@ -24,15 +20,6 @@
android:name="io.flutter.embedding.android.NormalTheme" android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" android:resource="@style/NormalTheme"
/> />
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
@ -44,4 +31,15 @@
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
</application> </application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility?hl=en and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest> </manifest>

View File

@ -3,7 +3,7 @@
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on --> <!-- 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"> <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame --> the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.

View File

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

View File

@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.fahrecker.time_progress_calculator"> package="com.fahrecker.time_progress_calculator">
<!-- Flutter needs it to communicate with the running application <!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>

View File

@ -1,20 +1,7 @@
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects { allprojects {
repositories { repositories {
google() google()
jcenter() mavenCentral()
} }
} }
@ -26,6 +13,6 @@ subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }
task clean(type: Delete) { tasks.register("clean", Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

View File

@ -1,4 +1,3 @@
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx4G
android.enableR8=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true

View File

@ -1,6 +1,5 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip

View File

@ -1,11 +1,26 @@
include ':app' pluginManagement {
def flutterSdkPath = {
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties() def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk") def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties" assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" return flutterSdkPath
}
settings.ext.flutterSdkPath = flutterSdkPath()
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "7.3.0" apply false
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
}
include ":app"

2
ios/.gitignore vendored
View File

@ -1,3 +1,4 @@
**/dgph
*.mode1v3 *.mode1v3
*.mode2v3 *.mode2v3
*.moved-aside *.moved-aside
@ -18,6 +19,7 @@ Flutter/App.framework
Flutter/Flutter.framework Flutter/Flutter.framework
Flutter/Flutter.podspec Flutter/Flutter.podspec
Flutter/Generated.xcconfig Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx Flutter/app.flx
Flutter/app.zip Flutter/app.zip
Flutter/flutter_assets/ Flutter/flutter_assets/

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>8.0</string> <string>12.0</string>
</dict> </dict>
</plist> </plist>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1020" LastUpgradeVersion = "1510"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
@ -38,8 +36,19 @@
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions> <Testables>
</AdditionalOptions> <TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
@ -61,8 +70,6 @@
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Profile" buildConfiguration = "Profile"

View File

@ -41,7 +41,9 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<false/> <true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

59
lib/actions/actions.dart Normal file
View File

@ -0,0 +1,59 @@
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
class LoadSettingsAction {}
class AppSettingsLoadedActions {
final AppSettings appSettings;
AppSettingsLoadedActions(this.appSettings);
}
class UpdateAppSettingsActions {
final AppSettings appSettings;
UpdateAppSettingsActions(this.appSettings);
}
class AppSettingsNotLoadedAction {}
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.hasProgressesLoaded) {
store.dispatch(LoadTimeProgressListAction());
}
}
void loadSettingsIfUnloaded(Store<AppState> store) {
if (!store.state.hasSettingsLoaded) store.dispatch(LoadSettingsAction());
}

View File

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

17
lib/helper_functions.dart Normal file
View File

@ -0,0 +1,17 @@
import 'dart:ui';
import 'package:time_progress_tracker/models/time_progress.dart';
TimeProgress selectProgressById(List<TimeProgress> tpList, String id) =>
tpList.firstWhere((tp) => tp.id == id, orElse: () => TimeProgress.initialDefault());
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);
}

View File

@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:time_progress_tracker/app.dart'; import 'package:time_progress_tracker/app.dart';
import 'package:time_progress_tracker/redux/app_state.dart'; import 'package:time_progress_tracker/middleware/store_middleware.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/app_settings.dart';
import 'package:time_progress_tracker/persistence/time_progress.dart'; import 'package:time_progress_tracker/persistence/time_progress_repository.dart';
import 'package:time_progress_tracker/redux/reducers/app_state_reducer.dart'; import 'package:time_progress_tracker/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();

View File

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

View File

@ -1,5 +1,5 @@
class TimeProgressInvalidNameException implements Exception { class TimeProgressInvalidNameException implements Exception {
final invalidName; final String invalidName;
TimeProgressInvalidNameException(this.invalidName); TimeProgressInvalidNameException(this.invalidName);
@ -7,8 +7,8 @@ class TimeProgressInvalidNameException implements Exception {
} }
class TimeProgressStartTimeIsNotBeforeEndTimeException implements Exception { class TimeProgressStartTimeIsNotBeforeEndTimeException implements Exception {
final startTime; final DateTime startTime;
final endTime; final DateTime endTime;
TimeProgressStartTimeIsNotBeforeEndTimeException( TimeProgressStartTimeIsNotBeforeEndTimeException(
this.startTime, this.endTime); this.startTime, this.endTime);

View File

@ -13,6 +13,12 @@ class AppSettings {
required this.duration, required this.duration,
}); });
factory AppSettings.defaults() => const AppSettings(
doneColor: Colors.green,
leftColor: Colors.red,
duration: Duration(days: 365),
);
AppSettings copyWith({ AppSettings copyWith({
Color? doneColor, Color? doneColor,
Color? leftColor, Color? leftColor,

View File

@ -1,7 +1,6 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:time_progress_tracker/models/app_settings.dart'; 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/utils/constants.dart';
@immutable @immutable
class AppState { class AppState {
@ -9,22 +8,24 @@ class AppState {
final List<TimeProgress> timeProgressList; final List<TimeProgress> timeProgressList;
final AppSettings appSettings; final AppSettings appSettings;
AppState( const AppState(
{this.hasProgressesLoaded = false, {this.hasProgressesLoaded = false,
this.hasSettingsLoaded = false, this.hasSettingsLoaded = false,
this.timeProgressList = const [], this.timeProgressList = const [],
this.appSettings = defaultAppSettings}); required this.appSettings});
factory AppState.initial() => factory AppState.initial() =>
AppState(hasProgressesLoaded: false, appSettings: defaultAppSettings); AppState(hasProgressesLoaded: false, appSettings: AppSettings.defaults());
AppState copyWith({ AppState copyWith({
bool? hasLoaded, bool? hasLoaded,
List<TimeProgress>? timeProgressList, List<TimeProgress>? timeProgressList,
AppSettings? appSettings,
}) { }) {
return AppState( return AppState(
hasProgressesLoaded: hasLoaded ?? this.hasProgressesLoaded, hasProgressesLoaded: hasLoaded ?? hasProgressesLoaded,
timeProgressList: timeProgressList ?? this.timeProgressList, timeProgressList: timeProgressList ?? this.timeProgressList,
appSettings: appSettings ?? this.appSettings,
); );
} }

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.dart'; import 'package:time_progress_tracker/persistence/time_progress_entity.dart';
import 'package:time_progress_tracker/uuid.dart'; import 'package:time_progress_tracker/uuid.dart';
@immutable @immutable
@ -38,7 +38,7 @@ class TimeProgress {
int allDays() => endTime.difference(startTime).inDays; int allDays() => endTime.difference(startTime).inDays;
double percentDone() { double percentDone() {
double percent = this.daysBehind() / (this.allDays() / 100) / 100; double percent = daysBehind() / (allDays() / 100) / 100;
if (percent < 0) percent = 0; if (percent < 0) percent = 0;
if (percent > 1) percent = 1; if (percent > 1) percent = 1;
return percent; return percent;
@ -48,7 +48,7 @@ class TimeProgress {
DateTime.now().millisecondsSinceEpoch > startTime.millisecondsSinceEpoch; DateTime.now().millisecondsSinceEpoch > startTime.millisecondsSinceEpoch;
int daysTillStart() { int daysTillStart() {
if (hasStarted()) throw new TimeProgressHasStartedException(); if (hasStarted()) throw TimeProgressHasStartedException();
return startTime.difference(DateTime.now()).inDays; return startTime.difference(DateTime.now()).inDays;
} }
@ -56,7 +56,7 @@ class TimeProgress {
DateTime.now().millisecondsSinceEpoch > endTime.millisecondsSinceEpoch; DateTime.now().millisecondsSinceEpoch > endTime.millisecondsSinceEpoch;
int daysSinceEnd() { int daysSinceEnd() {
if (!hasEnded()) throw new TimeProgressHasNotEndedException(); if (!hasEnded()) throw TimeProgressHasNotEndedException();
return DateTime.now().difference(endTime).inDays; return DateTime.now().difference(endTime).inDays;
} }
@ -79,20 +79,19 @@ class TimeProgress {
"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)) {
throw new TimeProgressInvalidNameException(name); throw TimeProgressInvalidNameException(name);
if (!TimeProgress.areTimesValid(startTime, endTime)) }
throw new TimeProgressStartTimeIsNotBeforeEndTimeException( if (!TimeProgress.areTimesValid(startTime, endTime)) {
throw TimeProgressStartTimeIsNotBeforeEndTimeException(
startTime, endTime); startTime, endTime);
}
return TimeProgressEntity(id, name, startTime, endTime); return TimeProgressEntity(id, name, startTime, endTime);
} }
static TimeProgress fromEntity(TimeProgressEntity entity) => TimeProgress( static TimeProgress fromEntity(TimeProgressEntity entity) =>
entity.name, TimeProgress(entity.name, entity.startTime, entity.endTime,
entity.startTime, id: entity.id);
entity.endTime,
id: entity.id,
);
static bool isValid(TimeProgress tp) => static bool isValid(TimeProgress tp) =>
TimeProgress.isNameValid(tp.name) && TimeProgress.isNameValid(tp.name) &&

View File

@ -1,25 +1,26 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:time_progress_tracker/models/app_settings.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> { class AppSettingsRepository {
static const String _key = "app_settings"; static const String _key = "app_settings";
final SharedPreferences prefs;
final JsonCodec codec;
AppSettingsRepository(SharedPreferences prefs) : super(prefs); AppSettingsRepository(this.prefs, {this.codec = json});
@override Future<AppSettingsEntity> loadAppSettings() {
Future<AppSettingsEntity> load() { final String? jsonString = prefs.getString(_key);
final String? jsonString = this.prefs.getString(_key); if (jsonString == null) {
if (jsonString == null)
return Future<AppSettingsEntity>.value(AppSettingsEntity.defaults()); return Future<AppSettingsEntity>.value(AppSettingsEntity.defaults());
}
return Future<AppSettingsEntity>.value( return Future<AppSettingsEntity>.value(
AppSettingsEntity.fromJson(codec.decode(jsonString))); AppSettingsEntity.fromJson(codec.decode(jsonString)));
} }
@override Future<bool> saveAppSettings(AppSettingsEntity appSettings) =>
Future<bool> save(AppSettingsEntity appSettings) => prefs.setString(_key, codec.encode(appSettings));
this.prefs.setString(_key, codec.encode(appSettings));
} }
class AppSettingsEntity { class AppSettingsEntity {
@ -31,7 +32,7 @@ class AppSettingsEntity {
AppSettingsEntity( AppSettingsEntity(
this.doneColorValue, this.leftColorValue, this.durationDays); this.doneColorValue, this.leftColorValue, this.durationDays);
factory AppSettingsEntity.defaults() => defaultAppSettings.toEntity(); factory AppSettingsEntity.defaults() => AppSettings.defaults().toEntity();
@override @override
int get hashCode => doneColorValue.hashCode ^ leftColorValue.hashCode; int get hashCode => doneColorValue.hashCode ^ leftColorValue.hashCode;

View File

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

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

@ -0,0 +1,41 @@
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(dynamic 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

@ -0,0 +1,28 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:time_progress_tracker/persistence/time_progress_entity.dart';
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 = prefs.getString(_key);
if (jsonString == null) {
return Future<List<TimeProgressEntity>>.value([]);
}
return Future<List<TimeProgressEntity>>.value(codec
.decode(jsonString)["timers"]
.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 prefs.setString(_key, jsonString);
}
}

View File

@ -0,0 +1,35 @@
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_tracker/reducers/has_loaded_reducer.dart';
import 'package:time_progress_tracker/reducers/time_progress_list_reducer.dart';
AppState appStateReducer(AppState state, dynamic action) {
return AppState(
hasSettingsLoaded:
hasSettingsLoadedReducer(state.hasSettingsLoaded, action),
hasProgressesLoaded:
hasProgressesLoadedReducer(state.hasProgressesLoaded, action),
timeProgressList: timeProgressListReducer(state.timeProgressList, action),
appSettings: appSettingsReducers(state.appSettings, action),
);
}
final appSettingsReducers = combineReducers<AppSettings>([
TypedReducer<AppSettings, AppSettingsLoadedActions>(_loadAppSettings).call,
TypedReducer<AppSettings, UpdateAppSettingsActions>(_updateAppSettings).call,
TypedReducer<AppSettings, AppSettingsNotLoadedAction>(_setDefaultSettings).call
]);
AppSettings _loadAppSettings(
AppSettings appSettings, AppSettingsLoadedActions nS) =>
nS.appSettings;
AppSettings _setDefaultSettings(
AppSettings appSettings, AppSettingsNotLoadedAction action) =>
AppSettings.defaults();
AppSettings _updateAppSettings(
AppSettings appSettings, UpdateAppSettingsActions nS) =>
nS.appSettings;

View File

@ -0,0 +1,28 @@
import 'package:redux/redux.dart';
import 'package:time_progress_tracker/actions/actions.dart';
final hasProgressesLoadedReducer = combineReducers<bool>([
TypedReducer<bool, TimeProgressListLoadedAction>(_setProgressesLoaded).call,
TypedReducer<bool, TimeProgressListNotLoadedAction>(_setProgressesUnloaded).call
]);
bool _setProgressesLoaded(bool hasLoaded, TimeProgressListLoadedAction action) {
return true;
}
bool _setProgressesUnloaded(bool hasLoaded, TimeProgressListNotLoadedAction action) {
return false;
}
final hasSettingsLoadedReducer = combineReducers<bool>([
TypedReducer<bool, AppSettingsLoadedActions>(_setSettingsLoaded).call,
TypedReducer<bool, AppSettingsNotLoadedAction>(_setSettingsUnloaded).call
]);
bool _setSettingsLoaded(bool hasLoaded, AppSettingsLoadedActions action) {
return true;
}
bool _setSettingsUnloaded(bool hasLoaded, AppSettingsNotLoadedAction action) {
return false;
}

View File

@ -0,0 +1,45 @@
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).call,
TypedReducer<List<TimeProgress>, TimeProgressListNotLoadedAction>(
_setEmptyTimeProgressList).call,
TypedReducer<List<TimeProgress>, AddTimeProgressAction>(_addTimeProgress).call,
TypedReducer<List<TimeProgress>, UpdateTimeProgressAction>(
_updateTimeProgress).call,
TypedReducer<List<TimeProgress>, DeleteTimeProgressAction>(_deleteTimeProgress).call,
]);
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

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

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

@ -1,29 +0,0 @@
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);
}

View File

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

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

@ -1,63 +0,0 @@
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,53 +0,0 @@
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,52 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/widgets/buttons/create_progress_button.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";
const HomeScreen({super.key});
@override
State<StatefulWidget> createState() {
return _HomeScreenState();
}
}
class _HomeScreenState extends State<HomeScreen> {
int _currentIndex = 0;
final List<Widget> _children = [
const HomeActiveProgressesTab(),
const HomeInactiveProgressesTab(),
const HomeSettingsTab(),
];
void onBottomTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
@override
Widget build(BuildContext context) {
final ThemeData appTheme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text(HomeScreen.title),
backgroundColor: appTheme.colorScheme.primary,
),
body: _children[_currentIndex],
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: _currentIndex != 2 ? const CreateProgressButton() : null,
bottomNavigationBar: HomeBottomNavBar(
currentIndex: _currentIndex,
onTap: onBottomTabTapped,
),
);
}
}

View File

@ -0,0 +1,125 @@
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_settings.dart';
import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/selectors/time_progress_selectors.dart';
import 'package:time_progress_tracker/widgets/progress_editor_widget.dart';
class ProgressCreationScreen extends StatefulWidget {
static const routeName = "/create-progress";
static const title = "Create Time Progress";
const ProgressCreationScreen({super.key});
@override
State<StatefulWidget> createState() {
return _ProgressCreationScreenState();
}
}
class _ProgressCreationScreenState extends State<ProgressCreationScreen> {
TimeProgress? timeProgressToCreate;
bool _isProgressValid = false;
void initTimeProgress(TimeProgress timeProgress) {
if (timeProgressToCreate == null) {
setState(() {
timeProgressToCreate = timeProgress;
});
}
}
void onTimeProgressChanged(
TimeProgress newTimeProgress, bool isNewProgressValid) {
setState(() {
timeProgressToCreate = newTimeProgress;
_isProgressValid = isNewProgressValid;
});
}
@override
Widget build(BuildContext context) {
final ThemeData appTheme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text(ProgressCreationScreen.title),
backgroundColor: appTheme.colorScheme.primary,
),
body: Container(
padding: const EdgeInsets.all(12),
child: StoreConnector<AppState, _ViewModel>(
onInit: loadSettingsIfUnloaded,
converter: (store) => _ViewModel.create(store),
builder: (context, _ViewModel viewModel) {
WidgetsBinding.instance.addPostFrameCallback((_) {
initTimeProgress(viewModel.defaultDurationProgress);
});
return ProgressEditorWidget(
timeProgress:
timeProgressToCreate ?? viewModel.defaultDurationProgress,
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",
onPressed: _isProgressValid
? () {
vm.onAddTimeProgress(
timeProgressToCreate ?? vm.defaultDurationProgress);
Navigator.pop(context);
}
: null,
child: const Icon(Icons.save),
),
),
),
Expanded(
child: FloatingActionButton(
heroTag: "cancelTimeProgressCreationBTN",
onPressed: () {
Navigator.pop(context);
},
child: const Icon(Icons.cancel),
),
)
],
),
);
}
}
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,136 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/screens/home_screen.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';
import 'package:time_progress_tracker/widgets/store_connectors/settings_store_connector.dart';
import 'package:time_progress_tracker/widgets/store_connectors/time_progress_store_connector.dart';
class ProgressDetailScreenArguments {
final String id;
ProgressDetailScreenArguments(this.id);
}
class ProgressDetailScreen extends StatefulWidget {
static const routeName = "/progress";
static const title = "Progress View";
const ProgressDetailScreen({super.key});
@override
State<StatefulWidget> createState() {
return _ProgressDetailScreenState();
}
}
class _ProgressDetailScreenState extends State<ProgressDetailScreen> {
bool _editMode = false, _isEditedProgressValid = false;
TimeProgress? _editedProgress, _originalProgress;
void _initEditedProgress(TimeProgress tp) {
if (_editedProgress == null) {
_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) {
List<Widget> columnChildren = [];
if (!_editMode) {
columnChildren.add(Expanded(
child: ProgressViewWidget(
timeProgress: _editMode ? _editedProgress ?? tpVm.tp : tpVm.tp,
doneColor: settingsVm.appSettings.doneColor,
leftColor: settingsVm.appSettings.leftColor,
)));
} else {
columnChildren.add(Expanded(
child: ProgressEditorWidget(
timeProgress: _editedProgress ?? tpVm.tp,
onTimeProgressChanged: _onEditedProgressChanged,
)));
}
return columnChildren;
}
@override
Widget build(BuildContext context) {
final ThemeData appTheme = Theme.of(context);
final ProgressDetailScreenArguments args = ModalRoute.of(context)
?.settings
.arguments as ProgressDetailScreenArguments;
return Scaffold(
appBar: AppBar(
title: const Text(ProgressDetailScreen.title),
backgroundColor: appTheme.colorScheme.primary,
),
body: SettingsStoreConnector(
loadedBuilder: (context, settingsVm) {
return TimeProgressStoreConnector(
timeProgressId: args.id,
loadedBuilder: (context, tpVm) {
_initEditedProgress(tpVm.tp);
return Container(
margin: const EdgeInsets.all(8),
child: Column(
children: _renderColumnChildren(settingsVm, tpVm),
),
);
},
);
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: TimeProgressStoreConnector(
timeProgressId: args.id,
loadedBuilder: (context, tpVm) {
void saveEditedProgress() {
tpVm.updateTimeProgress(_editedProgress ?? tpVm.tp);
_switchEditMode(false);
}
void deleteTimeProgress() {
tpVm.deleteTimeProgress();
Navigator.popUntil(
context, ModalRoute.withName(HomeScreen.routeName));
}
return DetailScreenFloatingActionButtons(
editMode: _editMode,
originalProgress: tpVm.tp,
editedProgress: _editedProgress ?? tpVm.tp,
isEditedProgressValid: _isEditedProgressValid,
onEditProgress: () => _switchEditMode(true),
onSaveEditedProgress: saveEditedProgress,
onCancelEditProgress: _cancelEditMode,
onDeleteProgress: deleteTimeProgress);
},
),
);
}
}

View File

@ -1,8 +1,8 @@
import 'dart:ui'; import 'dart:ui';
import 'package:time_progress_tracker/models/app_settings.dart'; import 'package:time_progress_tracker/models/app_settings.dart';
import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_tracker/models/time_progress.dart'; import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/redux/app_state.dart';
List<TimeProgress> timeProgressListSelector(AppState state) => List<TimeProgress> timeProgressListSelector(AppState state) =>
state.timeProgressList; state.timeProgressList;
@ -46,11 +46,10 @@ List<TimeProgress> pastTimeProgressesSelector(AppState state) =>
.toList(); .toList();
TimeProgress? timeProgressByIdSelector(AppState state, String id) { TimeProgress? timeProgressByIdSelector(AppState state, String id) {
if (state.timeProgressList.length < 1) return null; if (state.timeProgressList.isEmpty) return null;
TimeProgress tp = state.timeProgressList.firstWhere( return state.timeProgressList.firstWhere(
(timeProgress) => timeProgress.id == id, (timeProgress) => timeProgress.id == id,
orElse: () => TimeProgress.initialDefault()); orElse: () => TimeProgress.initialDefault());
return tp != TimeProgress.initialDefault() ? tp : null;
} }
AppSettings appSettingsSelector(AppState state) { AppSettings appSettingsSelector(AppState state) {

View File

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

@ -5,12 +5,12 @@ class AppYesNoDialog extends StatelessWidget {
final String contentText; final String contentText;
final void Function() onYesPressed; final void Function() onYesPressed;
AppYesNoDialog({ const AppYesNoDialog({
Key? key, super.key,
required this.titleText, required this.titleText,
required this.contentText, required this.contentText,
required this.onYesPressed, required this.onYesPressed,
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -18,15 +18,15 @@ class AppYesNoDialog extends StatelessWidget {
title: Text(titleText), title: Text(titleText),
content: Text(contentText), content: Text(contentText),
actions: <Widget>[ actions: <Widget>[
FlatButton( TextButton(
child: Text("Yes"),
onPressed: onYesPressed, onPressed: onYesPressed,
child: const Text("Yes"),
), ),
FlatButton( TextButton(
child: Text("No"),
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
}, },
child: const Text("No"),
) )
], ],
); );

View File

@ -1,13 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:time_progress_tracker/utils/helper_functions.dart'; import 'package:time_progress_tracker/helper_functions.dart';
class ColorPickerButton extends StatelessWidget { class ColorPickerButton extends StatelessWidget {
final String title, dialogTitle; final String title, dialogTitle;
final Color selectedColor; final Color selectedColor;
final void Function(Color) onColorPicked; final void Function(Color) onColorPicked;
ColorPickerButton({ const ColorPickerButton({
super.key,
required this.title, required this.title,
required this.dialogTitle, required this.dialogTitle,
required this.selectedColor, required this.selectedColor,
@ -34,13 +35,13 @@ class ColorPickerButton extends StatelessWidget {
}, },
); );
}, },
child: Text(title),
style: TextButton.styleFrom( style: TextButton.styleFrom(
primary: useBrightBackground(selectedColor) foregroundColor: useBrightBackground(selectedColor)
? appTheme.primaryTextTheme.button!.color ? appTheme.primaryTextTheme.labelLarge?.color
: appTheme.textTheme.button!.color, : appTheme.textTheme.labelLarge?.color,
backgroundColor: selectedColor, backgroundColor: selectedColor,
), ),
child: Text(title),
); );
} }
} }

View File

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/screens/progress_creation_screen.dart';
class CreateProgressButton extends StatelessWidget {
final String _heroTag = "createProgressBTN";
const CreateProgressButton({super.key});
@override
Widget build(BuildContext context) {
void onButtonPressed() =>
Navigator.pushNamed(context, ProgressCreationScreen.routeName);
return FloatingActionButton(
heroTag: _heroTag,
onPressed: onButtonPressed,
child: const Icon(Icons.add),
);
}
}

View File

@ -3,21 +3,22 @@ import 'package:flutter/material.dart';
class DatePickerBtn extends StatelessWidget { class DatePickerBtn extends StatelessWidget {
final String leadingString; final String leadingString;
final DateTime pickedDate; final DateTime pickedDate;
final void Function(DateTime) onDatePicked; final void Function(DateTime?) onDatePicked;
DatePickerBtn({ const DatePickerBtn({
super.key,
required this.leadingString, required this.leadingString,
required this.pickedDate, required this.pickedDate,
required this.onDatePicked, required this.onDatePicked,
}) : super(); });
void _onButtonPressed(BuildContext context) async { void _onButtonPressed(BuildContext context) async {
onDatePicked((await showDatePicker( onDatePicked(await showDatePicker(
context: context, context: context,
initialDate: pickedDate, initialDate: pickedDate,
firstDate: DateTime(1900), firstDate: DateTime(1900),
lastDate: DateTime(2100), lastDate: DateTime(2100),
))!); ));
} }
@override @override
@ -25,12 +26,12 @@ class DatePickerBtn extends StatelessWidget {
ThemeData appTheme = Theme.of(context); ThemeData appTheme = Theme.of(context);
return TextButton( return TextButton(
onPressed: () => _onButtonPressed(context), onPressed: () => _onButtonPressed(context),
style: TextButton.styleFrom(
foregroundColor: appTheme.primaryTextTheme.labelLarge?.color,
backgroundColor: appTheme.colorScheme.secondary,
),
child: Text( child: Text(
"$leadingString ${pickedDate.toLocal().toString().split(" ")[0]}"), "$leadingString ${pickedDate.toLocal().toString().split(" ")[0]}"),
style: TextButton.styleFrom(
primary: appTheme.primaryTextTheme.button!.color,
backgroundColor: appTheme.accentColor,
),
); );
} }
} }

View File

@ -5,7 +5,8 @@ class SelectDurationBtn extends StatelessWidget {
final Duration duration; final Duration duration;
final void Function(Duration) updateDuration; final void Function(Duration) updateDuration;
SelectDurationBtn({ const SelectDurationBtn({
super.key,
required this.duration, required this.duration,
required this.updateDuration, required this.updateDuration,
}); });
@ -25,7 +26,7 @@ class SelectDurationBtn extends StatelessWidget {
]), ]),
hideHeader: false, hideHeader: false,
title: const Text("Default Duration"), title: const Text("Default Duration"),
selectedTextStyle: TextStyle(color: appTheme.accentColor), selectedTextStyle: TextStyle(color: appTheme.colorScheme.secondary),
onConfirm: _onPickerConfirm) onConfirm: _onPickerConfirm)
.showModal(context); .showModal(context);
@ -38,10 +39,10 @@ class SelectDurationBtn extends StatelessWidget {
int days = duration.inDays - (365 * years) - (30 * months); int days = duration.inDays - (365 * years) - (30 * months);
return TextButton( return TextButton(
onPressed: () => _onButtonPressed(context, appTheme), onPressed: () => _onButtonPressed(context, appTheme),
child: Text("$years Years $months Months $days Days"),
style: TextButton.styleFrom( style: TextButton.styleFrom(
primary: appTheme.primaryTextTheme.button!.color, foregroundColor: appTheme.primaryTextTheme.labelLarge?.color,
backgroundColor: appTheme.accentColor, backgroundColor: appTheme.colorScheme.secondary,
)); ),
child: Text("$years Years $months Months $days Days"));
} }
} }

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/ui/app_yes_no_dialog_widget.dart'; import 'package:time_progress_tracker/widgets/app_yes_no_dialog_widget.dart';
class DetailScreenFloatingActionButtons extends StatelessWidget { class DetailScreenFloatingActionButtons extends StatelessWidget {
final bool editMode, isEditedProgressValid; final bool editMode, isEditedProgressValid;
@ -10,7 +10,8 @@ class DetailScreenFloatingActionButtons extends StatelessWidget {
onCancelEditProgress, onCancelEditProgress,
onDeleteProgress; onDeleteProgress;
DetailScreenFloatingActionButtons({ const DetailScreenFloatingActionButtons({
super.key,
required this.editMode, required this.editMode,
required this.originalProgress, required this.originalProgress,
required this.editedProgress, required this.editedProgress,
@ -25,10 +26,10 @@ class DetailScreenFloatingActionButtons extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData appTheme = Theme.of(context); final ThemeData appTheme = Theme.of(context);
void _onCancelEditTimeProgressBTN() { void onCancelEditTimeProgressBTN() {
if (originalProgress == editedProgress) if (originalProgress == editedProgress) {
onCancelEditProgress(); onCancelEditProgress();
else { } else {
showDialog( showDialog(
context: context, context: context,
builder: (_) => AppYesNoDialog( builder: (_) => AppYesNoDialog(
@ -44,7 +45,7 @@ class DetailScreenFloatingActionButtons extends StatelessWidget {
} }
} }
void _onDeleteTimeProgressBTN() { void onDeleteTimeProgressBTN() {
showDialog( showDialog(
context: context, context: context,
builder: (_) => AppYesNoDialog( builder: (_) => AppYesNoDialog(
@ -61,13 +62,13 @@ class DetailScreenFloatingActionButtons extends StatelessWidget {
child: FloatingActionButton( child: FloatingActionButton(
heroTag: heroTag:
editMode ? "saveEditedTimeProgressBTN" : "editTimeProgressBTN", editMode ? "saveEditedTimeProgressBTN" : "editTimeProgressBTN",
child: editMode ? Icon(Icons.save) : Icon(Icons.edit), backgroundColor: editMode ? Colors.green : appTheme.colorScheme.secondary,
backgroundColor: editMode ? Colors.green : appTheme.accentColor,
onPressed: editMode onPressed: editMode
? isEditedProgressValid ? isEditedProgressValid
? onSaveEditedProgress ? onSaveEditedProgress
: null : null
: onEditProgress, : onEditProgress,
child: editMode ? const Icon(Icons.save) : const Icon(Icons.edit),
), ),
), ),
Expanded( Expanded(
@ -75,11 +76,11 @@ class DetailScreenFloatingActionButtons extends StatelessWidget {
heroTag: editMode heroTag: editMode
? "cancelEditTimeProgressBTN" ? "cancelEditTimeProgressBTN"
: "deleteTimeProgressBTN", : "deleteTimeProgressBTN",
child: editMode ? Icon(Icons.cancel) : Icon(Icons.delete),
backgroundColor: Colors.red, backgroundColor: Colors.red,
onPressed: editMode onPressed: editMode
? _onCancelEditTimeProgressBTN ? onCancelEditTimeProgressBTN
: _onDeleteTimeProgressBTN, : onDeleteTimeProgressBTN,
child: editMode ? const Icon(Icons.cancel) : const Icon(Icons.delete),
), ),
), ),
], ],

View File

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

View File

@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/helper_functions.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/widgets/progress_list_view/progress_list_view.dart';
import 'package:time_progress_tracker/widgets/store_connectors/settings_store_connector.dart';
import 'package:time_progress_tracker/widgets/store_connectors/time_progress_list_store_connector.dart';
class HomeActiveProgressesTab extends StatelessWidget {
const HomeActiveProgressesTab({super.key});
@override
Widget build(BuildContext context) {
return SettingsStoreConnector(
loadedBuilder: (context, settingsVm) {
return TimeProgressListStoreConnector(
loadedBuilder: (context, tpListVm) {
List<TimeProgress> activeTpList =
selectActiveProgresses(tpListVm.tpList);
if (activeTpList.isEmpty) {
return Container(
padding: const EdgeInsets.all(16),
child: const Center(
child: Text(
"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,39 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/helper_functions.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/widgets/progress_list_view/progress_list_view.dart';
import 'package:time_progress_tracker/widgets/store_connectors/settings_store_connector.dart';
import 'package:time_progress_tracker/widgets/store_connectors/time_progress_list_store_connector.dart';
class HomeInactiveProgressesTab extends StatelessWidget {
const HomeInactiveProgressesTab({super.key});
@override
Widget build(BuildContext context) {
return SettingsStoreConnector(
loadedBuilder: (context, settingsVm) {
return TimeProgressListStoreConnector(
loadedBuilder: (context, tpListVm) {
List<TimeProgress> inactiveTpList =
selectInactiveProgresses(tpListVm.tpList);
if (inactiveTpList.isEmpty) {
return Container(
padding: const EdgeInsets.all(16),
child: const Center(
child: Text(
"You don't have any currently inactive time progresses, that are tracked."),
),
);
}
return ProgressListView(
timeProgressList: inactiveTpList,
doneColor: settingsVm.appSettings.doneColor,
leftColor: settingsVm.appSettings.leftColor,
);
},
);
},
);
}
}

View File

@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:time_progress_tracker/app.dart';
import 'package:time_progress_tracker/widgets/home/tabs/settings/color_settings_widget.dart';
import 'package:time_progress_tracker/widgets/home/tabs/settings/duration_settings_widget.dart';
import 'package:time_progress_tracker/widgets/store_connectors/settings_store_connector.dart';
class HomeSettingsTab extends StatelessWidget {
const HomeSettingsTab({super.key});
@override
Widget build(BuildContext context) {
return SettingsStoreConnector(
loadedBuilder: (context, settingsVm) {
return Container(
padding: const EdgeInsets.all(16),
child: Center(
child: Column(
children: [
Expanded(
child: ColorSettingsWidget(
doneColor: settingsVm.appSettings.doneColor,
leftColor: settingsVm.appSettings.leftColor,
updateDoneColor: settingsVm.updateDoneColor,
updateLeftColor: settingsVm.updateLeftColor,
),
),
Expanded(
child: DurationSettingsWidget(
duration: settingsVm.appSettings.duration,
updateDuration: settingsVm.updateDuration,
),
),
const Spacer(),
Expanded(
child: TextButton(
onPressed: () {
showAboutDialog(
context: context,
applicationName: TimeProgressTrackerApp.name,
applicationVersion: "Beta",
applicationLegalese:
'\u00a9Andreas Fahrecker 2020-2021');
},
child: const Text("About"),
),
),
],
),
),
);
},
);
}
}

View File

@ -1,11 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:time_progress_tracker/ui/buttons/color_picker_btn.dart'; import 'package:time_progress_tracker/widgets/buttons/color_picker_btn.dart';
class ColorSettingsWidget extends StatelessWidget { class ColorSettingsWidget extends StatelessWidget {
final Color doneColor, leftColor; final Color doneColor, leftColor;
final void Function(Color) updateDoneColor, updateLeftColor; final void Function(Color) updateDoneColor, updateLeftColor;
ColorSettingsWidget({ const ColorSettingsWidget({
super.key,
required this.doneColor, required this.doneColor,
required this.leftColor, required this.leftColor,
required this.updateDoneColor, required this.updateDoneColor,
@ -21,14 +22,14 @@ class ColorSettingsWidget extends StatelessWidget {
Expanded( Expanded(
child: Text( child: Text(
"Color Settings", "Color Settings",
style: appTheme.textTheme.headline6, style: appTheme.textTheme.titleLarge,
), ),
), ),
Row( Row(
children: [ children: [
Expanded( Expanded(
child: Padding( child: Padding(
padding: EdgeInsets.only(right: 5), padding: const EdgeInsets.only(right: 5),
child: ColorPickerButton( child: ColorPickerButton(
title: "Done Color", title: "Done Color",
dialogTitle: "Select Done Color", dialogTitle: "Select Done Color",
@ -39,7 +40,7 @@ class ColorSettingsWidget extends StatelessWidget {
), ),
Expanded( Expanded(
child: Padding( child: Padding(
padding: EdgeInsets.only(left: 5), padding: const EdgeInsets.only(left: 5),
child: ColorPickerButton( child: ColorPickerButton(
title: "Left Color", title: "Left Color",
dialogTitle: "Select Left Color", dialogTitle: "Select Left Color",

View File

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

View File

@ -1,13 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:flutter/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/ui/buttons/date_picker_btn.dart'; import 'package:time_progress_tracker/widgets/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({ const ProgressEditorWidget({
super.key,
required this.timeProgress, required this.timeProgress,
required this.onTimeProgressChanged, required this.onTimeProgressChanged,
}); });
@ -32,7 +33,10 @@ class _ProgressEditorWidgetState extends State<ProgressEditorWidget> {
}); });
} }
void _onStartDateChanged(DateTime newStartDate) { void _onStartDateChanged(DateTime? newStartDate) {
if (newStartDate == null) {
return;
}
TimeProgress newProgress = TimeProgress newProgress =
widget.timeProgress.copyWith(startTime: newStartDate); widget.timeProgress.copyWith(startTime: newStartDate);
widget.onTimeProgressChanged( widget.onTimeProgressChanged(
@ -43,7 +47,10 @@ class _ProgressEditorWidgetState extends State<ProgressEditorWidget> {
}); });
} }
void _onEndDateChanged(DateTime newEndDate) { void _onEndDateChanged(DateTime? newEndDate) {
if (newEndDate == null) {
return;
}
TimeProgress newProgress = TimeProgress newProgress =
widget.timeProgress.copyWith(endTime: newEndDate); widget.timeProgress.copyWith(endTime: newEndDate);
widget.onTimeProgressChanged( widget.onTimeProgressChanged(
@ -63,36 +70,29 @@ class _ProgressEditorWidgetState extends State<ProgressEditorWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
double heightFactor = (!_validDate) ? 0.3 : 0.5;
List<Widget> columnChildren = [ List<Widget> columnChildren = [
Expanded( SizedBox(
child: Center( height: MediaQuery.of(context).size.height * heightFactor,
child: PlatformTextField( child: TextField(
controller: _nameTextController, controller: _nameTextController,
material: (context, platform) => MaterialTextFieldData(
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: "Progress Name", labelText: "Progress Name",
errorText: _validName errorText: _validName
? null ? null
: "The Name need to have at least 3 and at max 20 symbols.", : "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),
)
), ),
), SizedBox(
), height: MediaQuery.of(context).size.height * heightFactor,
),
Expanded(
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
child: Padding( child: Padding(
padding: EdgeInsets.only(right: 5), padding: const EdgeInsets.only(right: 5),
child: DatePickerBtn( child: DatePickerBtn(
leadingString: "Start Date:", leadingString: "Start Date:",
pickedDate: widget.timeProgress.startTime, pickedDate: widget.timeProgress.startTime,
@ -102,7 +102,7 @@ class _ProgressEditorWidgetState extends State<ProgressEditorWidget> {
), ),
Expanded( Expanded(
child: Padding( child: Padding(
padding: EdgeInsets.only(left: 5), padding: const EdgeInsets.only(left: 5),
child: DatePickerBtn( child: DatePickerBtn(
leadingString: "End Date:", leadingString: "End Date:",
pickedDate: widget.timeProgress.endTime, pickedDate: widget.timeProgress.endTime,
@ -115,10 +115,11 @@ class _ProgressEditorWidgetState extends State<ProgressEditorWidget> {
) )
]; ];
if (!_validDate) if (!_validDate) {
columnChildren.add( columnChildren.add(
Expanded( SizedBox(
child: Center( height: MediaQuery.of(context).size.height * heightFactor,
child: const Center(
child: Text( child: Text(
"Invalid Dates. The Start Date has to be before the End Date", "Invalid Dates. The Start Date has to be before the End Date",
style: TextStyle(color: Colors.red), style: TextStyle(color: Colors.red),
@ -126,8 +127,10 @@ class _ProgressEditorWidgetState extends State<ProgressEditorWidget> {
), ),
), ),
); );
}
return Container( return SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Column( child: Column(
children: columnChildren, children: columnChildren,
), ),

View File

@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/screens/progress_detail_screen.dart';
class 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 ProgressListTile extends StatelessWidget {
final TimeProgress timeProgress;
final Color doneColor, leftColor;
const ProgressListTile({super.key,
required this.timeProgress,
required this.doneColor,
required this.leftColor,
});
Widget _renderSubtitle(BuildContext context) {
if (!timeProgress.hasStarted()) {
return Text(ProgressListTileStrings.startsInDaysString(timeProgress));
}
if (timeProgress.hasEnded()) {
return Text(ProgressListTileStrings.endedDaysAgoString(timeProgress));
}
return LinearPercentIndicator(
center: Text(ProgressListTileStrings.percentString(timeProgress)),
percent: timeProgress.percentDone(),
progressColor: doneColor,
backgroundColor: leftColor,
lineHeight: 20,
);
}
@override
Widget build(BuildContext context) {
void onTileTap() =>
Navigator.pushNamed(context, ProgressDetailScreen.routeName,
arguments: ProgressDetailScreenArguments(timeProgress.id));
return ListTile(
title: Text(timeProgress.name),
subtitle: _renderSubtitle(context),
onTap: onTileTap,
);
}
}

View File

@ -1,39 +1,34 @@
import 'dart:io' show Platform;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/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/ui/progress/progress_list_item.dart'; import 'package:time_progress_tracker/widgets/progress_list_view/progress_list_tile.dart';
class ProgressListView extends StatelessWidget { class ProgressListView extends StatelessWidget {
final List<TimeProgress> timeProgressList; final List<TimeProgress> timeProgressList;
final Color doneColor, leftColor; final Color doneColor, leftColor;
ProgressListView({ const ProgressListView({
super.key,
required this.timeProgressList, required this.timeProgressList,
required this.doneColor, required this.doneColor,
required this.leftColor, 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() { List<Widget> _renderListViewChildren() {
return timeProgressList return timeProgressList
.map((e) => _renderListTile(e)) .map((e) => Card(
child: ProgressListTile(
timeProgress: e,
doneColor: doneColor,
leftColor: leftColor,
),
))
.toList(growable: false); .toList(growable: false);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView( return ListView(
padding: EdgeInsets.all(8), padding: const EdgeInsets.all(8),
children: _renderListViewChildren(), children: _renderListViewChildren(),
); );
} }

View File

@ -8,7 +8,8 @@ class ProgressViewWidget extends StatelessWidget {
final Color doneColor; final Color doneColor;
final Color leftColor; final Color leftColor;
ProgressViewWidget({ const ProgressViewWidget({
super.key,
required this.timeProgress, required this.timeProgress,
required this.doneColor, required this.doneColor,
required this.leftColor, required this.leftColor,
@ -16,23 +17,28 @@ class ProgressViewWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Column( child: Column(
children: [ children: [
Expanded( SizedBox(
height: MediaQuery.of(context).size.height *
0.3, // adjust the value as needed
child: FittedBox( child: FittedBox(
fit: BoxFit.fitWidth, fit: BoxFit.fitWidth,
child: Text( child: Text(
timeProgress.name, timeProgress.name,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black87, color: Colors.black87,
), ),
), ),
), ),
), ),
Expanded( SizedBox(
height: MediaQuery.of(context).size.height *
0.3, // adjust the value as needed
child: CircularPercentIndicator( child: CircularPercentIndicator(
radius: 100, radius: 100,
lineWidth: 10, lineWidth: 10,
@ -42,14 +48,16 @@ class ProgressViewWidget extends StatelessWidget {
center: Text("${(timeProgress.percentDone() * 100).floor()} %"), center: Text("${(timeProgress.percentDone() * 100).floor()} %"),
), ),
), ),
Expanded( SizedBox(
height: MediaQuery.of(context).size.height *
0.3, // adjust the value as needed
child: LinearPercentIndicator( child: LinearPercentIndicator(
padding: EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
percent: timeProgress.percentDone(), percent: timeProgress.percentDone(),
leading: Text("${timeProgress.daysBehind()} Days"), leading: Text("${timeProgress.daysBehind()} Days"),
center: Text( center: Text(
"${(timeProgress.percentDone() * 100).floor()} %", "${(timeProgress.percentDone() * 100).floor()} %",
style: TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
), ),
trailing: Text("${timeProgress.daysLeft()} Days"), trailing: Text("${timeProgress.daysLeft()} Days"),
progressColor: doneColor, progressColor: doneColor,

View File

@ -1,15 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/models/app_settings.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/models/app_state.dart';
import 'package:time_progress_tracker/redux/app_state.dart';
import 'package:time_progress_tracker/utils/helper_functions.dart';
class SettingsStoreConnector extends StatelessWidget { class SettingsStoreConnector extends StatelessWidget {
final Widget Function(BuildContext, SettingsViewModel) loadedBuilder; final Widget Function(BuildContext, SettingsViewModel) loadedBuilder;
SettingsStoreConnector({ const SettingsStoreConnector({
super.key,
required this.loadedBuilder, required this.loadedBuilder,
}); });
@ -19,10 +19,11 @@ class SettingsStoreConnector extends StatelessWidget {
onInit: loadSettingsIfUnloaded, onInit: loadSettingsIfUnloaded,
converter: (store) => SettingsViewModel._create(store), converter: (store) => SettingsViewModel._create(store),
builder: (context, SettingsViewModel vm) { builder: (context, SettingsViewModel vm) {
if (!vm.hasSettingsLoaded) if (!vm.hasSettingsLoaded) {
return Center( return const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
); );
}
return loadedBuilder(context, vm); return loadedBuilder(context, vm);
}, },
); );
@ -45,17 +46,17 @@ class SettingsViewModel {
); );
factory SettingsViewModel._create(Store<AppState> store) { factory SettingsViewModel._create(Store<AppState> store) {
AppSettings _appSettings = store.state.appSettings; AppSettings appSettings = store.state.appSettings;
void _updateDoneColor(Color dC) => store.dispatch( void updateDoneColor(Color dC) => store.dispatch(
UpdateAppSettingsActions(_appSettings.copyWith(doneColor: dC))); UpdateAppSettingsActions(appSettings.copyWith(doneColor: dC)));
void _updateLeftColor(Color lC) => store.dispatch( void updateLeftColor(Color lC) => store.dispatch(
UpdateAppSettingsActions(_appSettings.copyWith(leftColor: lC))); UpdateAppSettingsActions(appSettings.copyWith(leftColor: lC)));
void _updateDuration(Duration d) => store void updateDuration(Duration d) => store
.dispatch(UpdateAppSettingsActions(_appSettings.copyWith(duration: d))); .dispatch(UpdateAppSettingsActions(appSettings.copyWith(duration: d)));
return SettingsViewModel(_appSettings, store.state.hasSettingsLoaded, return SettingsViewModel(appSettings, store.state.hasSettingsLoaded,
_updateDoneColor, _updateLeftColor, _updateDuration); updateDoneColor, updateLeftColor, updateDuration);
} }
} }

View File

@ -1,14 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:time_progress_tracker/redux/app_state.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/models/time_progress.dart';
import 'package:time_progress_tracker/utils/helper_functions.dart';
class TimeProgressListStoreConnector extends StatelessWidget { class TimeProgressListStoreConnector extends StatelessWidget {
final Widget Function(BuildContext, TimeProgressListViewModel) loadedBuilder; final Widget Function(BuildContext, TimeProgressListViewModel) loadedBuilder;
TimeProgressListStoreConnector({ const TimeProgressListStoreConnector({
super.key,
required this.loadedBuilder, required this.loadedBuilder,
}); });
@ -18,10 +19,11 @@ class TimeProgressListStoreConnector extends StatelessWidget {
onInit: loadTimeProgressListIfUnloaded, onInit: loadTimeProgressListIfUnloaded,
converter: (store) => TimeProgressListViewModel._create(store), converter: (store) => TimeProgressListViewModel._create(store),
builder: (context, TimeProgressListViewModel vm) { builder: (context, TimeProgressListViewModel vm) {
if (!vm.hasTpListLoaded) if (!vm.hasTpListLoaded) {
return Center( return const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
); );
}
return loadedBuilder(context, vm); return loadedBuilder(context, vm);
}, },
); );

View File

@ -1,17 +1,18 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:time_progress_tracker/redux/actions/time_progress_actions.dart'; import 'package:time_progress_tracker/actions/actions.dart';
import 'package:time_progress_tracker/redux/app_state.dart'; import 'package:time_progress_tracker/models/app_state.dart';
import 'package:time_progress_tracker/models/time_progress.dart'; import 'package:time_progress_tracker/models/time_progress.dart';
import 'package:time_progress_tracker/utils/helper_functions.dart';
import '../../helper_functions.dart';
class TimeProgressStoreConnector extends StatelessWidget { class TimeProgressStoreConnector extends StatelessWidget {
final String timeProgressId; final String timeProgressId;
final Widget Function(BuildContext, TimeProgressViewModel) loadedBuilder; final Widget Function(BuildContext, TimeProgressViewModel) loadedBuilder;
TimeProgressStoreConnector({ const TimeProgressStoreConnector({
super.key,
required this.timeProgressId, required this.timeProgressId,
required this.loadedBuilder, required this.loadedBuilder,
}); });
@ -23,14 +24,11 @@ class TimeProgressStoreConnector extends StatelessWidget {
converter: (store) => converter: (store) =>
TimeProgressViewModel._create(store, timeProgressId), TimeProgressViewModel._create(store, timeProgressId),
builder: (context, TimeProgressViewModel vm) { builder: (context, TimeProgressViewModel vm) {
if (!vm.hasTpListLoaded) if (!vm.hasTpListLoaded) {
return Center( return const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
); );
if (vm.tp == null) }
return Center(
child: Text("Error Invalid Time Progress"),
);
return loadedBuilder(context, vm); return loadedBuilder(context, vm);
}, },
); );
@ -38,7 +36,7 @@ class TimeProgressStoreConnector extends StatelessWidget {
} }
class TimeProgressViewModel { class TimeProgressViewModel {
final TimeProgress? tp; final TimeProgress tp;
final bool hasTpListLoaded; final bool hasTpListLoaded;
final void Function(TimeProgress) updateTimeProgress; final void Function(TimeProgress) updateTimeProgress;
@ -52,15 +50,15 @@ class TimeProgressViewModel {
); );
factory TimeProgressViewModel._create(Store<AppState> store, String id) { factory TimeProgressViewModel._create(Store<AppState> store, String id) {
void _updateTimeProgress(TimeProgress tp) => void updateTimeProgress(TimeProgress tp) =>
store.dispatch(UpdateTimeProgressAction(id, tp)); store.dispatch(UpdateTimeProgressAction(id, tp));
void _deleteTimeProgress() => store.dispatch(DeleteTimeProgressAction(id)); void deleteTimeProgress() => store.dispatch(DeleteTimeProgressAction(id));
return TimeProgressViewModel( return TimeProgressViewModel(
selectProgressById(store.state.timeProgressList, id), selectProgressById(store.state.timeProgressList, id),
store.state.hasProgressesLoaded, store.state.hasProgressesLoaded,
_updateTimeProgress, updateTimeProgress,
_deleteTimeProgress, deleteTimeProgress,
); );
} }
} }

View File

@ -5,100 +5,122 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
url: "https://pub.dartlang.org" sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.13" version: "3.4.10"
args: args:
dependency: transitive dependency: transitive
description: description:
name: args name: args
url: "https://pub.dartlang.org" sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev"
source: hosted source: hosted
version: "1.6.0" version: "2.4.2"
async: async:
dependency: transitive dependency: transitive
description: description:
name: async name: async
url: "https://pub.dartlang.org" sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.0" version: "2.11.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
name: boolean_selector name: boolean_selector
url: "https://pub.dartlang.org" sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
characters: characters:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
url: "https://pub.dartlang.org" sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.3.0"
charcode: checked_yaml:
dependency: transitive dependency: transitive
description: description:
name: charcode name: checked_yaml
url: "https://pub.dartlang.org" sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "2.0.3"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
url: "https://pub.dev"
source: hosted
version: "0.4.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
name: clock name: clock
url: "https://pub.dartlang.org" sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.1"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
url: "https://pub.dartlang.org" sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted source: hosted
version: "1.15.0" version: "1.18.0"
convert: convert:
dependency: transitive dependency: transitive
description: description:
name: convert name: convert
url: "https://pub.dartlang.org" sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "3.1.1"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
name: crypto name: crypto
url: "https://pub.dartlang.org" sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "3.0.3"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
name: cupertino_icons name: cupertino_icons
url: "https://pub.dartlang.org" sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.3" version: "1.0.6"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.3.1"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
url: "https://pub.dartlang.org" sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "2.1.2"
file: file:
dependency: transitive dependency: transitive
description: description:
name: file name: file
url: "https://pub.dartlang.org" sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.0" version: "7.0.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -108,39 +130,43 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_colorpicker name: flutter_colorpicker
url: "https://pub.dartlang.org" sha256: "458a6ed8ea480eb16ff892aedb4b7092b2804affd7e046591fb03127e8d8ef8b"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.5" version: "1.0.3"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_launcher_icons name: flutter_launcher_icons
url: "https://pub.dartlang.org" sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.1" version: "0.13.1"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
url: "https://pub.dev"
source: hosted
version: "3.0.1"
flutter_picker: flutter_picker:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: HEAD ref: HEAD
resolved-ref: e95d121f54faba889fbf8a850c86dd5cf4aa5c5a resolved-ref: "4a88a436b64d043d2e5412d64035a9bbe7f9d45d"
url: "git://github.com/yangyxd/flutter_picker.git" url: "https://github.com/yangyxd/flutter_picker.git"
source: git source: git
version: "1.1.5" version: "2.1.1"
flutter_platform_widgets:
dependency: "direct main"
description:
name: flutter_platform_widgets
url: "https://pub.dartlang.org"
source: hosted
version: "0.80.0"
flutter_redux: flutter_redux:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_redux name: flutter_redux
url: "https://pub.dartlang.org" sha256: "3b20be9e08d0038e1452fbfa1fdb1ea0a7c3738c997734530b3c6d0bb5fcdbdc"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.0" version: "0.10.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -155,142 +181,218 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image name: image
url: "https://pub.dartlang.org" sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.19" version: "4.1.7"
js: js:
dependency: transitive dependency: transitive
description: description:
name: js name: js
url: "https://pub.dartlang.org" sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.3" version: "0.7.1"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
url: "https://pub.dev"
source: hosted
version: "4.8.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
url: "https://pub.dev"
source: hosted
version: "2.0.1"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
url: "https://pub.dev"
source: hosted
version: "2.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
url: "https://pub.dev"
source: hosted
version: "3.0.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
url: "https://pub.dartlang.org" sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.10" version: "0.12.16+1"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
url: "https://pub.dev"
source: hosted
version: "0.8.0"
meta: meta:
dependency: "direct main" dependency: "direct main"
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.11.0"
path: path:
dependency: transitive dependency: transitive
description: description:
name: path name: path
url: "https://pub.dartlang.org" sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.0" version: "1.9.0"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
name: path_provider_linux name: path_provider_linux
url: "https://pub.dartlang.org" sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.2.1"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: path_provider_platform_interface name: path_provider_platform_interface
url: "https://pub.dartlang.org" sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.1.2"
path_provider_windows: path_provider_windows:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
url: "https://pub.dartlang.org" sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.2.1"
percent_indicator: percent_indicator:
dependency: "direct main" dependency: "direct main"
description: description:
name: percent_indicator name: percent_indicator
url: "https://pub.dartlang.org" sha256: c37099ad833a883c9d71782321cb65c3a848c21b6939b6185f0ff6640d05814c
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.9+1" version: "4.2.3"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
name: petitparser name: petitparser
url: "https://pub.dartlang.org" sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "6.0.2"
platform: platform:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
url: "https://pub.dartlang.org" sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.1.4"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
url: "https://pub.dartlang.org" sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.1.8"
process: pointycastle:
dependency: transitive dependency: transitive
description: description:
name: process name: pointycastle
url: "https://pub.dartlang.org" sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.0" version: "3.7.4"
redux: redux:
dependency: "direct main" dependency: "direct main"
description: description:
name: redux name: redux
url: "https://pub.dartlang.org" sha256: "1e86ed5b1a9a717922d0a0ca41f9bf49c1a587d50050e9426fc65b14e85ec4d7"
url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0+3" version: "5.0.0"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
url: "https://pub.dartlang.org" sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.2.2"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
url: "https://pub.dev"
source: hosted
version: "2.3.5"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_linux name: shared_preferences_linux
url: "https://pub.dartlang.org" sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.3.2"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
shared_preferences_platform_interface: shared_preferences_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_platform_interface name: shared_preferences_platform_interface
url: "https://pub.dartlang.org" sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.3.2"
shared_preferences_web: shared_preferences_web:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
url: "https://pub.dartlang.org" sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.3.0"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_windows name: shared_preferences_windows
url: "https://pub.dartlang.org" sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.3.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -300,86 +402,114 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
url: "https://pub.dartlang.org" sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.0" version: "1.10.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
name: stack_trace name: stack_trace
url: "https://pub.dartlang.org" sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.11.1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
name: stream_channel name: stream_channel
url: "https://pub.dartlang.org" sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.2"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
name: string_scanner name: string_scanner
url: "https://pub.dartlang.org" sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.2.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
name: term_glyph name: term_glyph
url: "https://pub.dartlang.org" sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.1"
test_api: test_api:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
url: "https://pub.dartlang.org" sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.19" version: "0.6.1"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
url: "https://pub.dartlang.org" sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.2"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
url: "https://pub.dev"
source: hosted
version: "13.0.0"
web:
dependency: transitive
description:
name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
url: "https://pub.dartlang.org" sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "5.3.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
url: "https://pub.dartlang.org" sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0" version: "1.0.4"
xml: xml:
dependency: transitive dependency: transitive
description: description:
name: xml name: xml
url: "https://pub.dartlang.org" sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted source: hosted
version: "4.5.1" version: "6.5.0"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
name: yaml name: yaml
url: "https://pub.dartlang.org" sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "3.1.2"
sdks: sdks:
dart: ">=2.12.0 <3.0.0" dart: ">=3.3.1 <4.0.0"
flutter: ">=1.20.4" flutter: ">=3.19.0"

View File

@ -2,7 +2,7 @@ name: time_progress_tracker
description: A Flutter Application to create Timers with a percentage indicator. description: A Flutter Application to create Timers with a percentage indicator.
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application. # The following defines the version and build number for your application.
@ -12,37 +12,54 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# build by specifying --build-name and --build-number, respectively. # build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode. # In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.0.19+19 version: 0.0.21+21
environment: environment:
sdk: ">=2.12.0 <3.0.0" sdk: '>=3.3.1 <4.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_colorpicker: flutter_colorpicker:
flutter_redux: flutter_redux:
flutter_picker: flutter_picker:
git: git://github.com/yangyxd/flutter_picker.git git:
flutter_platform_widgets: url: https://github.com/yangyxd/flutter_picker.git
meta: meta:
percent_indicator: percent_indicator:
redux: redux:
shared_preferences: shared_preferences:
cupertino_icons: ^0.1.3
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.6
dev_dependencies: dev_dependencies:
flutter_launcher_icons: flutter_launcher_icons:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^3.0.0
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter. # The following section is specific to Flutter packages.
flutter: flutter:
# The following line ensures that the Material Icons font is # The following line ensures that the Material Icons font is
@ -56,7 +73,7 @@ flutter:
# - images/a_dot_ham.jpeg # - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware. # https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see # For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages # https://flutter.dev/assets-and-images/#from-packages

View File

@ -3,8 +3,9 @@ import 'package:flutter/material.dart';
class MaterialTesterWidget extends StatelessWidget { class MaterialTesterWidget extends StatelessWidget {
final Widget widget; final Widget widget;
MaterialTesterWidget({ const MaterialTesterWidget({
@required this.widget, super.key,
required this.widget,
}); });
@override @override

View File

@ -10,37 +10,37 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:percent_indicator/linear_percent_indicator.dart'; import 'package:percent_indicator/linear_percent_indicator.dart';
import 'package:time_progress_tracker/models/app_settings.dart'; 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/ui/progress/progress_list_item.dart'; import 'package:time_progress_tracker/widgets/progress_list_view/progress_list_tile.dart';
import 'package:time_progress_tracker/ui/progress/progress_list_view.dart'; import 'package:time_progress_tracker/widgets/progress_list_view/progress_list_view.dart';
import 'MaterialTesterWidget.dart'; import 'material_tester_widget.dart';
void main() { void main() {
final AppSettings _defaultAppSettings = AppSettings.defaults(); final AppSettings defaultAppSettings = AppSettings.defaults();
final int _thisYear = DateTime.now().year; final int thisYear = DateTime.now().year;
final TimeProgress _activeProgress = TimeProgress( final TimeProgress activeProgress = TimeProgress(
"TestProgress", DateTime(_thisYear - 2), DateTime(_thisYear + 2)); "TestProgress", DateTime(thisYear - 2), DateTime(thisYear + 2));
void _findStringOnce(String str) => expect(find.text(str), findsOneWidget); void findStringOnce(String str) => expect(find.text(str), findsOneWidget);
testWidgets("Progress List Tile with currently active progress works", testWidgets("Progress List Tile with currently active progress works",
(WidgetTester tester) async { (WidgetTester tester) async {
await tester.pumpWidget(MaterialTesterWidget( await tester.pumpWidget(MaterialTesterWidget(
widget: ProgressListItem( widget: ProgressListTile(
timeProgress: _activeProgress, timeProgress: activeProgress,
doneColor: _defaultAppSettings.doneColor, doneColor: defaultAppSettings.doneColor,
leftColor: _defaultAppSettings.leftColor, leftColor: defaultAppSettings.leftColor,
), ),
)); ));
_findStringOnce(_activeProgress.name); findStringOnce(activeProgress.name);
_findStringOnce(ProgressListTileStrings.percentString(_activeProgress)); findStringOnce(ProgressListTileStrings.percentString(activeProgress));
WidgetPredicate linearPercentPredicate = (Widget widget) => linearPercentPredicate(Widget widget) =>
widget is LinearPercentIndicator && widget is LinearPercentIndicator &&
widget.percent == _activeProgress.percentDone() && widget.percent == activeProgress.percentDone() &&
widget.progressColor == _defaultAppSettings.doneColor && widget.progressColor == defaultAppSettings.doneColor &&
widget.backgroundColor == _defaultAppSettings.leftColor; widget.backgroundColor == defaultAppSettings.leftColor;
expect(find.byWidgetPredicate(linearPercentPredicate), findsOneWidget); expect(find.byWidgetPredicate(linearPercentPredicate), findsOneWidget);
}); });
@ -48,46 +48,46 @@ void main() {
(WidgetTester tester) async { (WidgetTester tester) async {
TimeProgress futureProgress = TimeProgress( TimeProgress futureProgress = TimeProgress(
"Test Progress", "Test Progress",
DateTime(_thisYear + 1), DateTime(thisYear + 1),
DateTime(_thisYear + 2), DateTime(thisYear + 2),
); );
await tester.pumpWidget(MaterialTesterWidget( await tester.pumpWidget(MaterialTesterWidget(
widget: ProgressListItem( widget: ProgressListTile(
timeProgress: futureProgress, timeProgress: futureProgress,
doneColor: _defaultAppSettings.doneColor, doneColor: defaultAppSettings.doneColor,
leftColor: _defaultAppSettings.leftColor, leftColor: defaultAppSettings.leftColor,
), ),
)); ));
_findStringOnce(futureProgress.name); findStringOnce(futureProgress.name);
_findStringOnce(ProgressListTileStrings.startsInDaysString(futureProgress)); findStringOnce(ProgressListTileStrings.startsInDaysString(futureProgress));
}); });
testWidgets("Progress List Tile with past progress works", testWidgets("Progress List Tile with past progress works",
(WidgetTester tester) async { (WidgetTester tester) async {
TimeProgress pastProgress = TimeProgress( TimeProgress pastProgress = TimeProgress(
"Test Progress", "Test Progress",
DateTime(_thisYear - 2), DateTime(thisYear - 2),
DateTime(_thisYear - 1), DateTime(thisYear - 1),
); );
await tester.pumpWidget(MaterialTesterWidget( await tester.pumpWidget(MaterialTesterWidget(
widget: ProgressListItem( widget: ProgressListTile(
timeProgress: pastProgress, timeProgress: pastProgress,
doneColor: _defaultAppSettings.doneColor, doneColor: defaultAppSettings.doneColor,
leftColor: _defaultAppSettings.leftColor, leftColor: defaultAppSettings.leftColor,
), ),
)); ));
_findStringOnce(pastProgress.name); findStringOnce(pastProgress.name);
_findStringOnce(ProgressListTileStrings.endedDaysAgoString(pastProgress)); findStringOnce(ProgressListTileStrings.endedDaysAgoString(pastProgress));
}); });
WidgetPredicate getProgressListTilePredicate( WidgetPredicate getProgressListTilePredicate(
TimeProgress tp, AppSettings as) => TimeProgress tp, AppSettings as) =>
(Widget widget) => (Widget widget) =>
widget is ProgressListItem && widget is ProgressListTile &&
widget.timeProgress == tp && widget.timeProgress == tp &&
widget.doneColor == as.doneColor && widget.doneColor == as.doneColor &&
widget.leftColor == as.leftColor; widget.leftColor == as.leftColor;
@ -96,35 +96,37 @@ void main() {
(WidgetTester tester) async { (WidgetTester tester) async {
await tester.pumpWidget(MaterialTesterWidget( await tester.pumpWidget(MaterialTesterWidget(
widget: ProgressListView( widget: ProgressListView(
timeProgressList: [_activeProgress], timeProgressList: [activeProgress],
doneColor: _defaultAppSettings.doneColor, doneColor: defaultAppSettings.doneColor,
leftColor: _defaultAppSettings.leftColor, leftColor: defaultAppSettings.leftColor,
), ),
)); ));
_findStringOnce(_activeProgress.name); findStringOnce(activeProgress.name);
expect( expect(
find.byWidgetPredicate( find.byWidgetPredicate(
getProgressListTilePredicate(_activeProgress, _defaultAppSettings)), getProgressListTilePredicate(activeProgress, defaultAppSettings)),
findsOneWidget); findsOneWidget);
}); });
testWidgets("Progress List View displays file tiles", testWidgets("Progress List View displays file tiles",
(WidgetTester tester) async { (WidgetTester tester) async {
List<TimeProgress> tpList = []; List<TimeProgress> tpList = [];
for (int i = 0; i < 5; i++) tpList.add(_activeProgress); for (int i = 0; i < 5; i++) {
tpList.add(activeProgress);
}
await tester.pumpWidget(MaterialTesterWidget( await tester.pumpWidget(MaterialTesterWidget(
widget: ProgressListView( widget: ProgressListView(
timeProgressList: tpList, timeProgressList: tpList,
doneColor: _defaultAppSettings.doneColor, doneColor: defaultAppSettings.doneColor,
leftColor: _defaultAppSettings.leftColor, leftColor: defaultAppSettings.leftColor,
), ),
)); ));
expect(find.text(_activeProgress.name), findsNWidgets(5)); expect(find.text(activeProgress.name), findsNWidgets(5));
expect( expect(
find.byWidgetPredicate( find.byWidgetPredicate(
getProgressListTilePredicate(_activeProgress, _defaultAppSettings)), getProgressListTilePredicate(activeProgress, defaultAppSettings)),
findsNWidgets(5)); findsNWidgets(5));
}); });
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -8,10 +8,13 @@
The path provided below has to start and end with a slash "/" in order for The path provided below has to start and end with a slash "/" in order for
it to work correctly. it to work correctly.
Fore more details: For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
--> -->
<base href="/"> <base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta content="IE=Edge" http-equiv="X-UA-Compatible">
@ -28,18 +31,29 @@
<title>time_progress_tracker</title> <title>time_progress_tracker</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<script>
// The value below is injected by flutter build, do not touch.
const serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head> </head>
<body> <body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script> <script>
if ('serviceWorker' in navigator) { window.addEventListener('load', function(ev) {
window.addEventListener('flutter-first-frame', function () { // Download main.dart.js
navigator.serviceWorker.register('flutter_service_worker.js'); _flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
engineInitializer.initializeEngine().then(function(appRunner) {
appRunner.runApp();
}); });
} }
});
});
</script> </script>
<script src="main.dart.js" type="application/javascript"></script>
</body> </body>
</html> </html>

View File

@ -18,6 +18,18 @@
"src": "icons/Icon-512.png", "src": "icons/Icon-512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png" "type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
} }
] ]
} }