Initial (redacted) commit.
28
.gitignore
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
.obsidian/
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode
|
||||
app/untranslated_messages.json
|
||||
app/tests.json
|
||||
public/docs/*
|
24
.gitlab/issue_templates/Bug.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
### Summary
|
||||
|
||||
<!-- Summarize the bug encountered concisely. -->
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
<!-- Describe how one can reproduce the issue - this is very important. Please use an ordered list. -->
|
||||
|
||||
### What is the current _bug_ behavior?
|
||||
|
||||
<!-- Describe what actually happens. -->
|
||||
|
||||
### What is the expected _correct_ behavior?
|
||||
|
||||
<!-- Describe what you should see instead. -->
|
||||
|
||||
### Relevant logs and/or screenshots
|
||||
|
||||
<!-- Paste any relevant logs - please use code blocks (```) to format console output, logs, and code
|
||||
as it's tough to read otherwise. -->
|
||||
|
||||
### Possible fixes
|
||||
|
||||
<!-- If you can, link to the line of code that might be responsible for the problem. -->
|
22
.gitlab/issue_templates/Feature.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
## :rocket: Description
|
||||
### Problem to solve
|
||||
|
||||
<!-- What problem do we solve? Try to define the who/what/why of the opportunity as a user story. For example, "As a (who), I want (what), so I can (why/value)." -->
|
||||
|
||||
### Proposal
|
||||
|
||||
<!-- How are we going to solve the problem? Try to include the user journey! https://about.gitlab.com/handbook/journeys/#user-journey -->
|
||||
|
||||
### Further details
|
||||
|
||||
<!-- Include use cases, benefits, goals, or any other details that will help us understand the problem better. -->
|
||||
|
||||
## :art: Wireframe
|
||||
|
||||
## :link: Links
|
||||
|
||||
## :white_check_mark: Acceptance Criteria
|
||||
- [ ]
|
||||
|
||||
|
||||
|
26
.gitlab/issue_templates/Refactoring.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
## Summary
|
||||
|
||||
<!--
|
||||
Please briefly describe what part of the code base needs to be refactored.
|
||||
-->
|
||||
|
||||
## Improvements
|
||||
|
||||
<!--
|
||||
Explain the benefits of refactoring this code.
|
||||
See also https://about.gitlab.com/handbook/values/index.html#say-why-not-just-what
|
||||
-->
|
||||
|
||||
## Risks
|
||||
|
||||
<!--
|
||||
Please list features that can break because of this refactoring and how you intend to solve that.
|
||||
-->
|
||||
|
||||
## Involved components
|
||||
|
||||
<!--
|
||||
List files or directories that will be changed by the refactoring.
|
||||
-->
|
||||
|
||||
## Links
|
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
1
README.md
Normal file
|
@ -0,0 +1 @@
|
|||
Flutter app done for a uni project. Names redacted for privacy reasons.
|
43
app/.gitignore
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
45
app/.metadata
Normal file
|
@ -0,0 +1,45 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: android
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: ios
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: linux
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: macos
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: web
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
- platform: windows
|
||||
create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
|
||||
|
||||
# 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'
|
3
app/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# habitrack_app
|
||||
|
||||
A new Flutter project.
|
13
app/android/.gitignore
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
58
app/android/app/build.gradle
Normal file
|
@ -0,0 +1,58 @@
|
|||
plugins {
|
||||
id "com.android.application"
|
||||
id "kotlin-android"
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id "dev.flutter.flutter-gradle-plugin"
|
||||
}
|
||||
|
||||
def localProperties = new Properties()
|
||||
def localPropertiesFile = rootProject.file("local.properties")
|
||||
if (localPropertiesFile.exists()) {
|
||||
localPropertiesFile.withReader("UTF-8") { reader ->
|
||||
localProperties.load(reader)
|
||||
}
|
||||
}
|
||||
|
||||
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
|
||||
if (flutterVersionCode == null) {
|
||||
flutterVersionCode = "1"
|
||||
}
|
||||
|
||||
def flutterVersionName = localProperties.getProperty("flutter.versionName")
|
||||
if (flutterVersionName == null) {
|
||||
flutterVersionName = "1.0"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.habitrack_app"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.example.habitrack_app"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutterVersionCode.toInteger()
|
||||
versionName = flutterVersionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
10
app/android/app/src/debug/AndroidManifest.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 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.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
48
app/android/app/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,48 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<application
|
||||
android:label="Habitrack"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility 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>
|
|
@ -0,0 +1,5 @@
|
|||
package com.example.habitrack_app
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity()
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
12
app/android/app/src/main/res/drawable/launch_background.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
BIN
app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 544 B |
BIN
app/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 442 B |
BIN
app/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 721 B |
BIN
app/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
app/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
app/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
18
app/android/app/src/main/res/values-night/styles.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
18
app/android/app/src/main/res/values/styles.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
7
app/android/app/src/profile/AndroidManifest.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 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.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
18
app/android/build.gradle
Normal file
|
@ -0,0 +1,18 @@
|
|||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = "../build"
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
3
app/android/gradle.properties
Normal file
|
@ -0,0 +1,3 @@
|
|||
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
5
app/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
25
app/android/settings.gradle
Normal file
|
@ -0,0 +1,25 @@
|
|||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return flutterSdkPath
|
||||
}()
|
||||
|
||||
includeBuild("$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"
|
BIN
app/assets/icon/Logo.webp
Normal file
After Width: | Height: | Size: 898 B |
BIN
app/assets/icon/image.png
Normal file
After Width: | Height: | Size: 248 KiB |
BIN
app/assets/icon/logo.jpg
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
app/assets/icon/logo.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
35
app/assets/water-bottle-svgrepo-com.svg
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 512 512" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M358.079,149.289c0-10.352-3.787-20.314-10.665-28.051l-55.449-62.38c-1.48-1.665-2.295-3.808-2.295-6.036V48.16
|
||||
c5.093-2.829,8.551-8.26,8.551-14.489V16.569C298.221,7.432,290.789,0,281.653,0h-51.307c-9.136,0-16.568,7.432-16.568,16.568
|
||||
V33.67c0,6.228,3.458,11.659,8.551,14.489v4.663c0,2.228-0.816,4.371-2.295,6.036l-55.449,62.381
|
||||
c-6.877,7.737-10.665,17.699-10.665,28.051v46.853c0,6.603,2.564,12.614,6.745,17.102c-4.18,4.488-6.745,10.5-6.745,17.102v8.551
|
||||
c0,6.603,2.564,12.614,6.745,17.102c-4.18,4.488-6.745,10.5-6.745,17.102v8.551c0,6.603,2.564,12.614,6.745,17.102
|
||||
c-4.18,4.488-6.745,10.5-6.745,17.102v8.551c0,6.603,2.564,12.614,6.745,17.102c-4.18,4.488-6.745,10.5-6.745,17.102v8.551
|
||||
c0,6.603,2.564,12.614,6.745,17.102c-4.18,4.488-6.745,10.5-6.745,17.102v8.551c0,6.603,2.564,12.614,6.745,17.102
|
||||
c-4.18,4.488-6.745,10.5-6.745,17.102v25.653c0,23.281,18.941,42.221,42.221,42.221h119.716c23.281,0,42.221-18.941,42.221-42.221
|
||||
v-25.653c0-6.603-2.564-12.614-6.745-17.102c4.18-4.488,6.745-10.5,6.745-17.102v-8.551c0-6.603-2.564-12.614-6.745-17.102
|
||||
c4.18-4.488,6.745-10.5,6.745-17.102v-8.551c0-6.603-2.564-12.614-6.745-17.102c4.18-4.488,6.745-10.5,6.745-17.102v-8.551
|
||||
c0-6.603-2.564-12.614-6.745-17.102c4.18-4.488,6.745-10.5,6.745-17.102v-8.551c0-6.603-2.564-12.614-6.745-17.102
|
||||
c4.18-4.488,6.745-10.5,6.745-17.102v-8.551c0-6.603-2.564-12.614-6.745-17.102c4.18-4.488,6.745-10.5,6.745-17.102V149.289z
|
||||
M229.812,16.568c0-0.295,0.239-0.534,0.534-0.534h51.307c0.295,0,0.534,0.239,0.534,0.534V33.67c0,0.295-0.239,0.534-0.534,0.534
|
||||
h-51.307c-0.295,0-0.534-0.239-0.534-0.534V16.568z M176.568,131.891l55.45-62.381c4.092-4.604,6.345-10.53,6.345-16.688v-2.585
|
||||
h35.273v2.584c0,6.158,2.253,12.085,6.345,16.689l55.449,62.381c1.346,1.514,2.476,3.176,3.427,4.928H173.143
|
||||
C174.093,135.066,175.224,133.403,176.568,131.891z M342.046,469.779c0,14.44-11.748,26.188-26.188,26.188H196.142
|
||||
c-14.44,0-26.188-11.748-26.188-26.188v-25.653c0-5.01,4.076-9.086,9.086-9.086H332.96c5.01,0,9.086,4.076,9.086,9.086V469.779z
|
||||
M342.046,409.921c0,5.01-4.076,9.086-9.086,9.086H179.04c-5.01,0-9.086-4.076-9.086-9.086v-8.551c0-5.01,4.076-9.086,9.086-9.086
|
||||
H332.96c5.01,0,9.086,4.076,9.086,9.086V409.921z M342.046,367.165c0,5.01-4.076,9.086-9.086,9.086H179.04
|
||||
c-5.01,0-9.086-4.076-9.086-9.086v-8.551c0-5.01,4.076-9.086,9.086-9.086H332.96c5.01,0,9.086,4.076,9.086,9.086V367.165z
|
||||
M342.046,324.409c0,5.01-4.076,9.086-9.086,9.086H179.04c-5.01,0-9.086-4.076-9.086-9.086v-8.551c0-5.01,4.076-9.086,9.086-9.086
|
||||
H332.96c5.01,0,9.086,4.076,9.086,9.086V324.409z M342.046,281.653c0,5.01-4.076,9.086-9.086,9.086H179.04
|
||||
c-5.01,0-9.086-4.076-9.086-9.086v-8.551c0-5.01,4.076-9.086,9.086-9.086H332.96c5.01,0,9.086,4.076,9.086,9.086V281.653z
|
||||
M342.046,238.898c0,5.01-4.076,9.086-9.086,9.086H179.04c-5.01,0-9.086-4.076-9.086-9.086v-8.551c0-5.01,4.076-9.086,9.086-9.086
|
||||
H332.96c5.01,0,9.086,4.076,9.086,9.086V238.898z M342.046,196.142c0,5.01-4.076,9.086-9.086,9.086H179.04
|
||||
c-5.01,0-9.086-4.076-9.086-9.086v-43.29h172.092V196.142z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
34
app/ios/.gitignore
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
**/dgph
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/ephemeral/
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
26
app/ios/Flutter/AppFrameworkInfo.plist
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
1
app/ios/Flutter/Debug.xcconfig
Normal file
|
@ -0,0 +1 @@
|
|||
#include "Generated.xcconfig"
|
1
app/ios/Flutter/Release.xcconfig
Normal file
|
@ -0,0 +1 @@
|
|||
#include "Generated.xcconfig"
|
616
app/ios/Runner.xcodeproj/project.pbxproj
Normal file
|
@ -0,0 +1,616 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||
);
|
||||
name = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146EF1CF9000F007C117D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = RunnerTests;
|
||||
productName = RunnerTests;
|
||||
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C8080294A63A400263BE5 = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||
};
|
||||
97C146ED1CF9000F007C117D = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 1100;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
331C807F294A63A400263BE5 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
331C807D294A63A400263BE5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.habitrackApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
331C8088294A63A400263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.habitrackApp.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
331C8089294A63A400263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.habitrackApp.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
331C808A294A63A400263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.habitrackApp.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147041CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
97C147061CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.habitrackApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.habitrackApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
331C8088294A63A400263BE5 /* Debug */,
|
||||
331C8089294A63A400263BE5 /* Release */,
|
||||
331C808A294A63A400263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
7
app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,98 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||
BuildableName = "RunnerTests.xctest"
|
||||
BlueprintName = "RunnerTests"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
7
app/ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
13
app/ios/Runner/AppDelegate.swift
Normal file
|
@ -0,0 +1,13 @@
|
|||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
122
app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
|
@ -0,0 +1,122 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 295 B |
After Width: | Height: | Size: 406 B |
After Width: | Height: | Size: 450 B |
After Width: | Height: | Size: 282 B |
After Width: | Height: | Size: 462 B |
After Width: | Height: | Size: 704 B |
After Width: | Height: | Size: 406 B |
After Width: | Height: | Size: 586 B |
After Width: | Height: | Size: 862 B |
After Width: | Height: | Size: 862 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 762 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
23
app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
After Width: | Height: | Size: 68 B |
BIN
app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
After Width: | Height: | Size: 68 B |
BIN
app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
After Width: | Height: | Size: 68 B |
5
app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
37
app/ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
26
app/ios/Runner/Base.lproj/Main.storyboard
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
49
app/ios/Runner/Info.plist
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Habitrack App</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>habitrack_app</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
1
app/ios/Runner/Runner-Bridging-Header.h
Normal file
|
@ -0,0 +1 @@
|
|||
#import "GeneratedPluginRegistrant.h"
|
12
app/ios/RunnerTests/RunnerTests.swift
Normal 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.
|
||||
}
|
||||
|
||||
}
|
3
app/l10n.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
arb-dir: lib/l10n
|
||||
template-arb-file: app_en.arb
|
||||
output-localization-file: app_localizations.dart
|
|
@ -0,0 +1,465 @@
|
|||
//import 'dart:ffi';
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart';
|
||||
import 'package:habitrack_app/function_widgets/widget_settings_menu/widget_settings.dart';
|
||||
import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart';
|
||||
import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart';
|
||||
import 'package:habitrack_app/main.dart';
|
||||
import 'package:habitrack_app/sembast/timer.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class CompoundWidgetTimer extends ConsumerStatefulWidget {
|
||||
CompoundWidgetTimer({required this.item, super.key});
|
||||
|
||||
TimerItem item;
|
||||
|
||||
double getProgress() {
|
||||
return (item.current / item.goal) / 60;
|
||||
}
|
||||
|
||||
@override
|
||||
ConsumerState<CompoundWidgetTimer> createState() =>
|
||||
_CompoundWidgetTimerState();
|
||||
}
|
||||
|
||||
class _CompoundWidgetTimerState extends ConsumerState<CompoundWidgetTimer>
|
||||
with WidgetsBindingObserver {
|
||||
_CompoundWidgetTimerState();
|
||||
|
||||
String button1text = '';
|
||||
Timer _timer = Timer(Duration.zero, () => ());
|
||||
|
||||
DateTime calcDue() {
|
||||
final now = DateTime.now();
|
||||
const secondsToAdd = 10;
|
||||
const duration = Duration(seconds: secondsToAdd);
|
||||
final futureTime = now.add(duration);
|
||||
|
||||
Logger().i(futureTime);
|
||||
|
||||
return DateTime.now();
|
||||
}
|
||||
|
||||
void handleButton1() {
|
||||
//start timer from 0
|
||||
const oneSec = Duration(seconds: 1);
|
||||
|
||||
if (widget.item.state == 'initial') {
|
||||
widget.item = widget.item.copyWith(state: 'running');
|
||||
ref.watch(homeControllerProvider).edit(widget.item);
|
||||
_timer = Timer.periodic(
|
||||
oneSec,
|
||||
(Timer funcTimer) {
|
||||
if (widget.item.current == (widget.item.goal * 60)) {
|
||||
setState(() {
|
||||
final now = DateTime.now();
|
||||
widget.item = widget.item.copyWith(
|
||||
state: 'completed',
|
||||
completedOn: now.toString(),
|
||||
);
|
||||
ref.watch(homeControllerProvider).edit(widget.item);
|
||||
|
||||
funcTimer.cancel();
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
widget.item =
|
||||
widget.item.copyWith(current: widget.item.current + 1);
|
||||
ref.watch(homeControllerProvider).edit(widget.item);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// continue timer
|
||||
else if (widget.item.state == 'paused') {
|
||||
widget.item = widget.item.copyWith(state: 'running');
|
||||
ref.watch(homeControllerProvider).edit(widget.item);
|
||||
_timer = Timer.periodic(
|
||||
const Duration(seconds: 1),
|
||||
(Timer funcTimer) {
|
||||
if (widget.item.current == (widget.item.goal * 60)) {
|
||||
setState(() {
|
||||
final now = DateTime.now();
|
||||
widget.item = widget.item.copyWith(
|
||||
state: 'completed',
|
||||
completedOn: now.toString(),
|
||||
);
|
||||
ref.watch(homeControllerProvider).edit(widget.item);
|
||||
|
||||
funcTimer.cancel();
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
widget.item =
|
||||
widget.item.copyWith(current: widget.item.current + 1);
|
||||
ref.watch(homeControllerProvider).edit(widget.item);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
} else if (widget.item.state == 'running') {
|
||||
widget.item = widget.item.copyWith(state: 'paused');
|
||||
ref.watch(homeControllerProvider).edit(widget.item);
|
||||
|
||||
_timer.cancel();
|
||||
} else if (widget.item.state == 'completed') {}
|
||||
}
|
||||
|
||||
void stopTimer() {
|
||||
setState(() {
|
||||
if (_timer.isActive) {
|
||||
_timer.cancel();
|
||||
}
|
||||
widget.item = widget.item.copyWith(
|
||||
current: 0,
|
||||
state: 'initial',
|
||||
);
|
||||
|
||||
ref.watch(homeControllerProvider).edit(widget.item);
|
||||
});
|
||||
}
|
||||
|
||||
String _formatCurrent() {
|
||||
final hours = (widget.item.current / 3600).floor();
|
||||
final minutes = ((widget.item.current - (hours * 3600)) / 60).floor();
|
||||
return '$hours hours : $minutes minutes';
|
||||
}
|
||||
|
||||
String _formattedTime() {
|
||||
final minutesTotal = widget.item.goal;
|
||||
|
||||
final hours = (minutesTotal / 60).floor();
|
||||
final minutes = minutesTotal - (hours * 60);
|
||||
return '$hours hours : $minutes minutes';
|
||||
}
|
||||
|
||||
void _toggleExpansion() {
|
||||
setState(() {
|
||||
widget.item = widget.item.copyWith(isExpanded: !widget.item.isExpanded);
|
||||
ref.watch(homeControllerProvider).edit(widget.item);
|
||||
});
|
||||
}
|
||||
|
||||
String _getButton1Text(BuildContext context) {
|
||||
if (widget.item.state == 'initial') {
|
||||
return AppLocalizations.of(context)!.timerWidget_buttonStart;
|
||||
} else if (widget.item.state == 'running') {
|
||||
return AppLocalizations.of(context)!.timerWidget_buttonPause;
|
||||
} else if (widget.item.state == 'paused') {
|
||||
return AppLocalizations.of(context)!.timerWidget_buttonContinue;
|
||||
}
|
||||
return 'Done';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref
|
||||
..watch(homeControllerProvider)
|
||||
..watch(itemsProvider);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10),
|
||||
padding: const EdgeInsets.only(left: 10, top: 15, right: 10, bottom: 15),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(7)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0x00000000).withOpacity(0.25),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 5,
|
||||
// changes position of shadow
|
||||
),
|
||||
],
|
||||
),
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (!widget.item.isExpanded) ...[
|
||||
Icon(
|
||||
Icons.timer,
|
||||
size: 20,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 10, right: 10),
|
||||
height: 30,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(5),
|
||||
), // Rounded corners
|
||||
child: LinearProgressIndicator(
|
||||
value:
|
||||
widget.getProgress(), // Progress value (0.0 - 1.0)
|
||||
backgroundColor:
|
||||
Colors.grey.withOpacity(0.5), // Background color
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||
Color(0xffA4E8FD),
|
||||
), // Progress color
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.item.name,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
// alignment: TextAlign.left,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (widget.item.isExpanded) ...[
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.item.name,
|
||||
textScaler: const TextScaler.linear(2),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
|
||||
// alignment: TextAlign.left,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
Icons.settings,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
onPressed: () => _showSettingsMenu(ref),
|
||||
),
|
||||
),
|
||||
],
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
widget.item.isExpanded
|
||||
? Icons.arrow_drop_up_outlined
|
||||
: Icons.arrow_drop_down_circle_outlined,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
onPressed: _toggleExpansion,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.item.isExpanded) ...[
|
||||
// Additional child elements when expanded
|
||||
SizedBox(
|
||||
height: 300,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
children: [
|
||||
Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
// color: Colors.blueAccent,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 250,
|
||||
child: SizedBox(
|
||||
height: 150,
|
||||
// color: Colors.greenAccent,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
// ignore: lines_longer_than_80_chars
|
||||
'${AppLocalizations.of(context)!.timerWidget_current}: ${_formatCurrent()} \n ${AppLocalizations.of(context)!.timerWidget_goal}: ${_formattedTime()}',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 10,
|
||||
left: MediaQuery.of(context).size.width * 0.15,
|
||||
width: MediaQuery.of(context).size.width * 0.60,
|
||||
height: 220,
|
||||
child: CircularProgressIndicator(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
strokeWidth: 7,
|
||||
value: widget.getProgress(),
|
||||
semanticsLabel: 'Circular progress indicator',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: handleButton1,
|
||||
child: Text(
|
||||
_getButton1Text(context),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: stopTimer,
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!
|
||||
.timerWidget_buttonReset,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Add more widgets here as needed
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showSettingsMenu(WidgetRef ref) async {
|
||||
logger.i('Opening settings');
|
||||
|
||||
final settingEntries = WidgetSettingsData(
|
||||
entries: {
|
||||
'name': SettingEntryText(
|
||||
name: AppLocalizations.of(context)!.widgetSettings_name,
|
||||
defaultValue: widget.item.name,
|
||||
),
|
||||
'duration': SettingEntryDuration(
|
||||
name: AppLocalizations.of(context)!.timerWidgetSettings_duration,
|
||||
defaultValue: widget.item.goal,
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
if (widget.item.state == 'running') {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context)!.timerWidget_pausedForEdit,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
content: WidgetSettings(
|
||||
entries: settingEntries,
|
||||
),
|
||||
actions: [
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
ref.watch(itemsProvider);
|
||||
final items = ref.watch(homeControllerProvider);
|
||||
|
||||
widget.item = widget.item.copyWith(isVisible: false);
|
||||
setState(() {
|
||||
items.edit(widget.item);
|
||||
});
|
||||
|
||||
logger.i('Attempting delete');
|
||||
// ignore: use_build_context_synchronously
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.widgetSettings_deleteButton,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
onPressed: () {
|
||||
_timer.cancel();
|
||||
ref.watch(homeControllerProvider);
|
||||
|
||||
logger.i('Attempting edit of name and/or duration');
|
||||
|
||||
final name = settingEntries.getValue('name') as String;
|
||||
logger.i('New name: $name');
|
||||
final duration = settingEntries.getValue('duration') as int;
|
||||
widget.item =
|
||||
widget.item.copyWith(goal: duration, name: name);
|
||||
setState(() {
|
||||
ref.watch(homeControllerProvider).edit(widget.item);
|
||||
});
|
||||
logger.i('NAME AND DURATION SUCCESSFULLY UPDATED');
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.widgetSettings_saveButton,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,968 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart';
|
||||
import 'package:habitrack_app/function_widgets/widget_settings_menu/widget_settings.dart';
|
||||
import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart';
|
||||
import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart';
|
||||
import 'package:habitrack_app/main.dart';
|
||||
import 'package:habitrack_app/sembast/tasks_list.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
const seperator = '_SEPARATOR_';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class IndividualTodoWidget extends ConsumerStatefulWidget {
|
||||
IndividualTodoWidget({
|
||||
required this.todo,
|
||||
required this.due,
|
||||
required this.completed,
|
||||
required this.item,
|
||||
required this.isVisible,
|
||||
required this.createdOn,
|
||||
required this.completedOn,
|
||||
super.key,
|
||||
});
|
||||
late String todo;
|
||||
late bool completed;
|
||||
late DateTime due;
|
||||
late DateTime createdOn;
|
||||
late DateTime completedOn;
|
||||
late TasksItem item;
|
||||
late bool isVisible;
|
||||
|
||||
@override
|
||||
ConsumerState<IndividualTodoWidget> createState() =>
|
||||
_IndividualTodoWidgetState();
|
||||
|
||||
String stringRepr() {
|
||||
return [
|
||||
todo,
|
||||
due.toString().substring(0, 10),
|
||||
createdOn,
|
||||
completedOn,
|
||||
completed,
|
||||
isVisible,
|
||||
].join(seperator);
|
||||
}
|
||||
|
||||
void deleteItem(WidgetRef ref) {
|
||||
final searchTerm = stringRepr();
|
||||
|
||||
if (completed == true) {
|
||||
final completedTaskList = <String>[...item.completedTaskList];
|
||||
//search in completed tasks list
|
||||
for (var i = 0; i < completedTaskList.length; i++) {
|
||||
if (searchTerm == completedTaskList.elementAt(i)) {
|
||||
var newItem = searchTerm;
|
||||
final toConvert = newItem.split(seperator);
|
||||
final todo = toConvert.elementAtOrNull(0)!;
|
||||
final due = DateTime.parse(toConvert.elementAtOrNull(1)!);
|
||||
final createdOn = DateTime.parse(toConvert.elementAtOrNull(2)!);
|
||||
final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!);
|
||||
|
||||
final completed =
|
||||
toConvert.elementAtOrNull(4)!.toLowerCase() == 'true';
|
||||
|
||||
final dateFormat = DateFormat('yyyy-MM-dd');
|
||||
final formattedDate = dateFormat.format(due);
|
||||
newItem = [
|
||||
todo,
|
||||
formattedDate.substring(0, 10),
|
||||
createdOn,
|
||||
completedOn,
|
||||
completed,
|
||||
false,
|
||||
].join(seperator);
|
||||
|
||||
completedTaskList[i] = newItem;
|
||||
final controller = ref.watch(homeControllerProvider);
|
||||
item = item.copyWith(completedTaskList: completedTaskList);
|
||||
controller.edit(item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final taskList = <String>[...item.taskList];
|
||||
for (var i = 0; i < taskList.length; i++) {
|
||||
if (searchTerm == taskList.elementAt(i)) {
|
||||
var newItem = searchTerm;
|
||||
final toConvert = newItem.split(seperator);
|
||||
final todo = toConvert.elementAtOrNull(0)!;
|
||||
final due = DateTime.parse(toConvert.elementAtOrNull(1)!);
|
||||
final createdOn = DateTime.parse(toConvert.elementAtOrNull(2)!);
|
||||
final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!);
|
||||
|
||||
final completed =
|
||||
toConvert.elementAtOrNull(4)!.toLowerCase() == 'true';
|
||||
|
||||
final dateFormat = DateFormat('yyyy-MM-dd');
|
||||
final formattedDate = dateFormat.format(due);
|
||||
newItem = [
|
||||
todo,
|
||||
formattedDate,
|
||||
createdOn,
|
||||
completedOn,
|
||||
completed,
|
||||
false,
|
||||
].join(seperator);
|
||||
|
||||
taskList[i] = newItem;
|
||||
final controller = ref.watch(homeControllerProvider);
|
||||
item = item.copyWith(taskList: taskList);
|
||||
|
||||
controller.edit(item);
|
||||
} else {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool overdue() {
|
||||
final now = DateTime.now();
|
||||
|
||||
if (DateTime(now.year, now.month, now.day).isAfter(due)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class _IndividualTodoWidgetState extends ConsumerState<IndividualTodoWidget> {
|
||||
_IndividualTodoWidgetState();
|
||||
|
||||
void _toggleChecked(bool? param) {
|
||||
if (widget.overdue() && !widget.completed) {
|
||||
} else {
|
||||
final searchTerm = widget.stringRepr();
|
||||
|
||||
final completedTaskList = <String>[...widget.item.completedTaskList];
|
||||
final taskList = <String>[...widget.item.taskList];
|
||||
if (widget.completed == true) {
|
||||
//search in completed tasks list
|
||||
for (var i = 0; i < completedTaskList.length; i++) {
|
||||
if (searchTerm == completedTaskList.elementAt(i)) {
|
||||
widget.completed = !widget.completed;
|
||||
final replacementStr = widget.stringRepr();
|
||||
|
||||
completedTaskList.removeAt(i);
|
||||
taskList.add(replacementStr);
|
||||
|
||||
final controller = ref.watch(homeControllerProvider);
|
||||
widget.item = widget.item.copyWith(
|
||||
taskList: taskList,
|
||||
completedTaskList: completedTaskList,
|
||||
);
|
||||
setState(() {
|
||||
controller.edit(widget.item);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < taskList.length; i++) {
|
||||
if (searchTerm == taskList.elementAt(i)) {
|
||||
widget.completed = !widget.completed;
|
||||
// ignore: cascade_invocations
|
||||
widget.completedOn = DateTime.now();
|
||||
final replacementStr = widget.stringRepr();
|
||||
|
||||
taskList.removeAt(i);
|
||||
completedTaskList.add(replacementStr);
|
||||
final controller = ref.watch(homeControllerProvider);
|
||||
widget.item = widget.item.copyWith(
|
||||
taskList: taskList,
|
||||
completedTaskList: completedTaskList,
|
||||
);
|
||||
setState(
|
||||
() {
|
||||
controller.edit(widget.item);
|
||||
},
|
||||
);
|
||||
} else {}
|
||||
}
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
void _editItem(String oldItem, String itemToAdd) {
|
||||
final completedTaskList = <String>[...widget.item.completedTaskList];
|
||||
final taskList = <String>[...widget.item.taskList];
|
||||
if (widget.completed == true) {
|
||||
//search in completed tasks list
|
||||
for (var i = 0; i < completedTaskList.length; i++) {
|
||||
if (oldItem == completedTaskList.elementAt(i)) {
|
||||
completedTaskList.replaceRange(i, i + 1, [itemToAdd]);
|
||||
final controller = ref.watch(homeControllerProvider);
|
||||
widget.item =
|
||||
widget.item.copyWith(completedTaskList: completedTaskList);
|
||||
setState(() {
|
||||
controller.edit(widget.item);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < taskList.length; i++) {
|
||||
if (oldItem == taskList.elementAt(i)) {
|
||||
taskList.replaceRange(i, i + 1, [itemToAdd]);
|
||||
|
||||
final controller = ref.watch(homeControllerProvider);
|
||||
widget.item = widget.item.copyWith(taskList: taskList);
|
||||
setState(
|
||||
() {
|
||||
controller.edit(widget.item);
|
||||
},
|
||||
);
|
||||
} else {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _editPopup(String oldItem) async {
|
||||
final todoFieldEditController = TextEditingController(text: widget.todo);
|
||||
DateTime? date = widget.due;
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.tasksWidget_editTask,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
scrollable: true,
|
||||
content: Column(
|
||||
children: <Widget>[
|
||||
TextField(
|
||||
controller: todoFieldEditController,
|
||||
decoration: InputDecoration(hintText: widget.todo),
|
||||
autofillHints: null,
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.taskSettings_duePicker,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
final pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: widget.due,
|
||||
firstDate: DateTime(2024, 0, 0),
|
||||
lastDate: DateTime(2101),
|
||||
);
|
||||
date = pickedDate;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: <Widget>[
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.widgetSettings_cancelButton,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
date ??= widget.due;
|
||||
if (todoFieldEditController.text.isEmpty) {
|
||||
todoFieldEditController.text = widget.todo;
|
||||
}
|
||||
|
||||
Navigator.of(context).pop();
|
||||
final dateFormat = DateFormat('yyyy-MM-dd');
|
||||
|
||||
final formattedDate = dateFormat.format(date!);
|
||||
|
||||
final newItem = [
|
||||
todoFieldEditController.text,
|
||||
formattedDate,
|
||||
widget.createdOn,
|
||||
widget.completedOn,
|
||||
widget.completed,
|
||||
widget.isVisible,
|
||||
].join(seperator);
|
||||
|
||||
_editItem(oldItem, newItem);
|
||||
|
||||
todoFieldEditController.clear();
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.widgetSettings_saveButton,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final df = DateFormat('dd.MM.yyyy');
|
||||
var dateText =
|
||||
// ignore: lines_longer_than_80_chars
|
||||
'${AppLocalizations.of(context)!.taskSettings_due}: ${df.format(widget.due)}';
|
||||
|
||||
var backgroundColor = Theme.of(context).colorScheme.secondary;
|
||||
if (widget.overdue() && !widget.completed) {
|
||||
dateText = AppLocalizations.of(context)!.tasksWidget_overdue;
|
||||
backgroundColor = Theme.of(context).colorScheme.tertiary;
|
||||
}
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 2),
|
||||
padding: const EdgeInsets.all(3),
|
||||
width: 300,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 1.5,
|
||||
),
|
||||
color: backgroundColor,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(7)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0x00000000).withOpacity(0.25),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 2,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: Checkbox(
|
||||
activeColor: Colors.green,
|
||||
fillColor: WidgetStateProperty.resolveWith(
|
||||
(states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return null;
|
||||
}
|
||||
return Theme.of(context).colorScheme.onSecondary;
|
||||
},
|
||||
),
|
||||
value: widget.completed,
|
||||
onChanged: _toggleChecked,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
widget.todo,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
dateText,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: IconButton(
|
||||
onPressed: () => _editPopup(widget.stringRepr()),
|
||||
icon: Icon(
|
||||
Icons.edit,
|
||||
color: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: IconButton(
|
||||
onPressed: () => setState(() {
|
||||
widget.deleteItem(ref);
|
||||
}),
|
||||
icon: Icon(
|
||||
Icons.delete,
|
||||
color: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class CompoundWidgetTasks extends ConsumerStatefulWidget {
|
||||
CompoundWidgetTasks({required this.item, super.key});
|
||||
|
||||
TasksItem item;
|
||||
|
||||
double getProgress() {
|
||||
if (item.completedTaskList.isNotEmpty) {
|
||||
return item.completedTaskList.length /
|
||||
(item.taskList.length + item.completedTaskList.length);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
ConsumerState<CompoundWidgetTasks> createState() =>
|
||||
_CompoundWidgetTasksState();
|
||||
}
|
||||
|
||||
class _CompoundWidgetTasksState extends ConsumerState<CompoundWidgetTasks> {
|
||||
final TextEditingController _todoFieldController = TextEditingController();
|
||||
|
||||
void _toggleExpansion() {
|
||||
setState(() {
|
||||
widget.item = widget.item.copyWith(isExpanded: !widget.item.isExpanded);
|
||||
ref.watch(homeControllerProvider).edit(widget.item);
|
||||
});
|
||||
}
|
||||
|
||||
IndividualTodoWidget _todoFromString(String input) {
|
||||
final toConvert = input.split(seperator);
|
||||
final todo = toConvert.elementAtOrNull(0)!;
|
||||
final due = DateTime.parse(toConvert.elementAtOrNull(1)!);
|
||||
final createdOn = DateTime.parse(toConvert.elementAtOrNull(2)!);
|
||||
final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!);
|
||||
|
||||
final completed = toConvert.elementAtOrNull(4)!.toLowerCase() == 'true';
|
||||
|
||||
final isVisible = toConvert.elementAt(5).toLowerCase() == 'true';
|
||||
|
||||
final newItem = IndividualTodoWidget(
|
||||
todo: todo,
|
||||
due: due,
|
||||
completed: completed,
|
||||
completedOn: completedOn,
|
||||
item: widget.item,
|
||||
isVisible: isVisible,
|
||||
createdOn: createdOn,
|
||||
);
|
||||
return newItem;
|
||||
}
|
||||
|
||||
List<Widget> _buildTodoList() {
|
||||
final items = <IndividualTodoWidget>[];
|
||||
|
||||
final taskList = <String>[...widget.item.taskList];
|
||||
final completedTaskList = <String>[...widget.item.completedTaskList];
|
||||
taskList.sort(
|
||||
(a, b) =>
|
||||
DateTime.parse(a.split(seperator).elementAtOrNull(1)!).compareTo(
|
||||
DateTime.parse(b.split(seperator).elementAtOrNull(1)!),
|
||||
),
|
||||
);
|
||||
completedTaskList.sort(
|
||||
(a, b) =>
|
||||
DateTime.parse(a.split(seperator).elementAtOrNull(1)!).compareTo(
|
||||
DateTime.parse(b.split(seperator).elementAtOrNull(1)!),
|
||||
),
|
||||
);
|
||||
for (var i = 0; i < taskList.length; i++) {
|
||||
final todoAsString = taskList.elementAt(i);
|
||||
|
||||
final newItem = _todoFromString(todoAsString);
|
||||
if (newItem.isVisible) {
|
||||
items.add(newItem);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < completedTaskList.length; i++) {
|
||||
final todoAsString = completedTaskList.elementAt(i);
|
||||
final newItem = _todoFromString(todoAsString);
|
||||
if (newItem.isVisible) {
|
||||
items.add(newItem);
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
void _addItem(String toAdd) {
|
||||
logger.i('ITEM TO ADD: $toAdd');
|
||||
final controller = ref.watch(homeControllerProvider);
|
||||
|
||||
final toCopy = <String>[...widget.item.taskList];
|
||||
// ignore: cascade_invocations
|
||||
toCopy.add(toAdd);
|
||||
widget.item = widget.item.copyWith(taskList: toCopy);
|
||||
setState(() {
|
||||
controller.edit(widget.item);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.watch(itemsProvider);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10),
|
||||
padding: const EdgeInsets.only(left: 10, top: 15, right: 10, bottom: 15),
|
||||
//height: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(7)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0x00000000).withOpacity(0.25),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 5,
|
||||
// changes position of shadow
|
||||
),
|
||||
],
|
||||
),
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (!widget.item.isExpanded) ...[
|
||||
Icon(
|
||||
Icons.task,
|
||||
size: 20,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 10, right: 10),
|
||||
// Width of the progress bar
|
||||
height: 30, // Height of the progress bar
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(5),
|
||||
), // Rounded corners
|
||||
child: LinearProgressIndicator(
|
||||
value:
|
||||
widget.getProgress(), // Progress value (0.0 - 1.0)
|
||||
backgroundColor:
|
||||
Colors.grey.withOpacity(0.5), // Background color
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||
Color(0xffA4E8FD),
|
||||
), // Progress color
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.item.name,
|
||||
style: TextStyle(
|
||||
// fontSize: 12,
|
||||
// fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (widget.item.isExpanded) ...[
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.item.name,
|
||||
textScaler: const TextScaler.linear(2),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
Icons.settings,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
onPressed: () => _showSettingsMenu(ref, context),
|
||||
),
|
||||
),
|
||||
],
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
widget.item.isExpanded
|
||||
? Icons.arrow_drop_up_outlined
|
||||
: Icons.arrow_drop_down_circle_outlined,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
onPressed: _toggleExpansion,
|
||||
),
|
||||
//child: Icon(Icons.arrow_drop_down_circle_outlined),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.item.isExpanded) ...[
|
||||
// Additional child elements when expanded
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: _addPopup,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.add_task_outlined,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
Text(
|
||||
// ignore: lines_longer_than_80_chars
|
||||
' ${AppLocalizations.of(context)!.tasksWidget_addTaskButtonLabel}',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
shrinkWrap: true,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
children: _buildTodoList(),
|
||||
),
|
||||
if (anyTasksCompleted()) ...[
|
||||
OutlinedButton(
|
||||
onPressed: _deleteCompletedTasks,
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!
|
||||
.tasksWidget_deleteDone,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
/* if (anyTasksOverdue()) ...[
|
||||
OutlinedButton(
|
||||
onPressed: _rescheduleOverdueTasks,
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!
|
||||
.tasksWidget_rescheduleOverdue,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],*/
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
bool anyTasksCompleted() {
|
||||
for (final item in widget.item.completedTaskList) {
|
||||
final toConvert = item.split(seperator);
|
||||
|
||||
final isVisible = toConvert.elementAt(5).toLowerCase() == 'true';
|
||||
|
||||
if (isVisible) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _deleteCompletedTasks() {
|
||||
final controller = ref.watch(homeControllerProvider);
|
||||
|
||||
final newCompletedTaskList = <String>[]; // widget.item.completedTaskList;
|
||||
for (final itemString in widget.item.completedTaskList) {
|
||||
final oldItem = _todoFromString(itemString);
|
||||
|
||||
final dateFormat = DateFormat('yyyy-MM-dd');
|
||||
|
||||
final newItem = [
|
||||
oldItem.todo,
|
||||
dateFormat.format(oldItem.due),
|
||||
oldItem.createdOn,
|
||||
oldItem.completedOn,
|
||||
oldItem.completed,
|
||||
false,
|
||||
].join(seperator);
|
||||
newCompletedTaskList.add(newItem);
|
||||
}
|
||||
widget.item = widget.item.copyWith(completedTaskList: newCompletedTaskList);
|
||||
controller.edit(widget.item);
|
||||
}
|
||||
|
||||
bool anyTasksOverdue() {
|
||||
final taskList = <String>[...widget.item.taskList];
|
||||
for (final taskString in taskList) {
|
||||
if (_todoFromString(taskString).overdue()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Used for the Reschedule Task Button
|
||||
// ignore: unused_element
|
||||
void _rescheduleOverdueTasks() {
|
||||
final taskList = <String>[...widget.item.taskList];
|
||||
|
||||
for (var i = 0; i < taskList.length; i++) {
|
||||
final taskString = taskList[i];
|
||||
if (_todoFromString(taskString).overdue()) {
|
||||
final oldItem = _todoFromString(taskString);
|
||||
final now = DateTime.now();
|
||||
final newItem = IndividualTodoWidget(
|
||||
todo: oldItem.todo,
|
||||
due: DateTime(now.year, now.month, now.day),
|
||||
createdOn: DateTime.now(),
|
||||
completedOn: DateTime.now(),
|
||||
completed: oldItem.completed,
|
||||
item: widget.item,
|
||||
isVisible: true,
|
||||
);
|
||||
taskList.replaceRange(i, i + 1, [newItem.stringRepr()]);
|
||||
|
||||
final controller = ref.watch(homeControllerProvider);
|
||||
widget.item = widget.item.copyWith(taskList: taskList);
|
||||
setState(
|
||||
() {
|
||||
controller.edit(widget.item);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addPopup() async {
|
||||
DateTime? date = DateTime.now();
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.tasksWidget_addTaskButtonLabel,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
scrollable: true,
|
||||
content: Column(
|
||||
children: <Widget>[
|
||||
TextField(
|
||||
controller: _todoFieldController,
|
||||
autofillHints: null,
|
||||
decoration: InputDecoration(
|
||||
hintText: AppLocalizations.of(context)!.taskSettings_name,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.taskSettings_duePicker,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
final pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(), //get today's date
|
||||
firstDate: DateTime.now(),
|
||||
//DateTime.now() - not to allow to choose before today
|
||||
lastDate: DateTime(2101),
|
||||
);
|
||||
date = pickedDate;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: <Widget>[
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.widgetSettings_cancelButton,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
//DateTime due = DateTime.parse(formattedDate);
|
||||
// ignore: cascade_invocations
|
||||
|
||||
if (_todoFieldController.text.isEmpty) {
|
||||
_todoFieldController.text = 'todo';
|
||||
}
|
||||
|
||||
if (date != null && _todoFieldController.text.isNotEmpty) {
|
||||
final dateFormat = DateFormat('yyyy-MM-dd');
|
||||
final formattedDate = dateFormat.format(date!);
|
||||
Navigator.of(context).pop();
|
||||
|
||||
final itemToAdd = [
|
||||
_todoFieldController.text,
|
||||
formattedDate,
|
||||
DateTime.now(),
|
||||
DateTime.now(),
|
||||
false,
|
||||
true,
|
||||
].join(seperator);
|
||||
|
||||
_addItem(itemToAdd);
|
||||
_todoFieldController.clear();
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.widgetSettings_saveButton,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showSettingsMenu(WidgetRef ref, BuildContext context) async {
|
||||
final settingEntries = WidgetSettingsData(
|
||||
entries: {
|
||||
'name': SettingEntryText(
|
||||
name: AppLocalizations.of(context)!.widgetSettings_name,
|
||||
defaultValue: widget.item.name,
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
content: WidgetSettings(
|
||||
entries: settingEntries,
|
||||
),
|
||||
actions: [
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
ref
|
||||
.watch(homeControllerProvider)
|
||||
.edit(widget.item.copyWith(isVisible: false));
|
||||
// await controller.delete(widget.item.id);
|
||||
|
||||
// ignore: use_build_context_synchronously
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.widgetSettings_deleteButton,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
final controller = ref.watch(homeControllerProvider);
|
||||
|
||||
final name = settingEntries.getValue('name') as String;
|
||||
|
||||
widget.item = widget.item.copyWith(name: name);
|
||||
setState(() {
|
||||
widget.item = widget.item.copyWith(name: name);
|
||||
|
||||
controller.edit(widget.item);
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.widgetSettings_saveButton,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,491 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart';
|
||||
import 'package:habitrack_app/function_widgets/widget_settings_menu/widget_settings.dart';
|
||||
import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart';
|
||||
import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart';
|
||||
import 'package:habitrack_app/main.dart';
|
||||
import 'package:habitrack_app/sembast/hydration.dart';
|
||||
import 'package:liquid_progress_indicator_v2/liquid_progress_indicator.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class CompoundWidgetWater extends ConsumerStatefulWidget {
|
||||
CompoundWidgetWater({required this.item, super.key});
|
||||
|
||||
late Hydration item;
|
||||
|
||||
double getProgress() {
|
||||
return item.current / item.goal;
|
||||
}
|
||||
|
||||
@override
|
||||
ConsumerState<CompoundWidgetWater> createState() =>
|
||||
_CompoundWidgetWaterState();
|
||||
}
|
||||
|
||||
class _CompoundWidgetWaterState extends ConsumerState<CompoundWidgetWater> {
|
||||
void _toggleExpansion() {
|
||||
setState(() {
|
||||
widget.item = widget.item.copyWith(isExpanded: !widget.item.isExpanded);
|
||||
ref.watch(homeControllerProvider).edit(widget.item);
|
||||
});
|
||||
}
|
||||
|
||||
void _addQuantity(int toAdd) {
|
||||
setState(() {
|
||||
final controller = ref.watch(homeControllerProvider);
|
||||
final oldval = widget.item.current;
|
||||
|
||||
logger.i('Old item: ${widget.item}');
|
||||
|
||||
widget.item = widget.item.copyWith(current: oldval + toAdd);
|
||||
logger.i('New item: ${widget.item}');
|
||||
|
||||
controller.edit(widget.item);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.watch(itemsProvider);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10),
|
||||
padding: const EdgeInsets.only(left: 10, top: 15, right: 10, bottom: 15),
|
||||
//height: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(7)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0x00000000).withOpacity(0.25),
|
||||
spreadRadius: 2,
|
||||
blurRadius: 5,
|
||||
// changes position of shadow
|
||||
),
|
||||
],
|
||||
),
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (!widget.item.isExpanded) ...[
|
||||
Icon(
|
||||
Icons.local_drink,
|
||||
size: 20,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 10, right: 10),
|
||||
// Width of the progress bar
|
||||
height: 30, // Height of the progress bar
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(5),
|
||||
), // Rounded corners
|
||||
child: LinearProgressIndicator(
|
||||
value:
|
||||
widget.getProgress(), // Progress value (0.0 - 1.0)
|
||||
backgroundColor:
|
||||
Colors.grey.withOpacity(0.5), // Background color
|
||||
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||
Color(0xffA4E8FD),
|
||||
), // Progress color
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.item.name,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (widget.item.isExpanded) ...[
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.item.name,
|
||||
textScaler: const TextScaler.linear(2),
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
Icons.settings,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
onPressed: () => _showSettingsMenu(ref),
|
||||
),
|
||||
//child: Icon(Icons.arrow_drop_down_circle_outlined),
|
||||
),
|
||||
],
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
widget.item.isExpanded
|
||||
? Icons.arrow_drop_up_outlined
|
||||
: Icons.arrow_drop_down_circle_outlined,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
onPressed: _toggleExpansion,
|
||||
),
|
||||
//child: Icon(Icons.arrow_drop_down_circle_outlined),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.item.isExpanded) ...[
|
||||
// Additional child elements when expanded
|
||||
SizedBox(
|
||||
height: 300,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
children: [
|
||||
//INSERT WIDGET SPECIFIC STUFF HERE
|
||||
SizedBox(
|
||||
height: 300,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OvershootLiquidLinearProgressIndicator(
|
||||
current: widget.item.current,
|
||||
goal: widget.item.goal,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'${widget.item.current / 1000} / ${widget.item.goal / 1000} L',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
onPressed: () => {
|
||||
_addQuantity(
|
||||
widget.item.button1Amount,
|
||||
),
|
||||
},
|
||||
child: Text(
|
||||
'${widget.item.button1Amount} mL',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
onPressed: () => {
|
||||
_addQuantity(
|
||||
widget.item.button2Amount,
|
||||
),
|
||||
},
|
||||
child: Text(
|
||||
'${widget.item.button2Amount} mL',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
onPressed: _showPopupCustomAmount,
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!
|
||||
.waterWidget_customAmountButton,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Add more widgets here as needed
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showSettingsMenu(WidgetRef ref) async {
|
||||
final settingEntries = WidgetSettingsData(
|
||||
entries: {
|
||||
'name': SettingEntryText(
|
||||
name: AppLocalizations.of(context)!.widgetSettings_name,
|
||||
defaultValue: widget.item.name,
|
||||
),
|
||||
'button1Amount': SettingEntryNumeric(
|
||||
name: AppLocalizations.of(context)!.waterWidgetSettings_button1,
|
||||
defaultValue: widget.item.button1Amount,
|
||||
),
|
||||
'button2Amount': SettingEntryNumeric(
|
||||
name: AppLocalizations.of(context)!.waterWidgetSettings_button2,
|
||||
defaultValue: widget.item.button2Amount,
|
||||
),
|
||||
'targetGoal': SettingEntrySlider(
|
||||
name: AppLocalizations.of(context)!.waterWidgetSettings_goal,
|
||||
defaultValue: double.parse(widget.item.goal.toString()),
|
||||
divisions: 80,
|
||||
topValue: 4000,
|
||||
),
|
||||
'currentAmount': SettingEntryNumeric(
|
||||
name: AppLocalizations.of(context)!.waterWidgetSettings_current,
|
||||
defaultValue: widget.item.current,
|
||||
),
|
||||
},
|
||||
);
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
content: WidgetSettings(
|
||||
entries: settingEntries,
|
||||
),
|
||||
actions: [
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
//widget.parent.delete(ref);
|
||||
ref.watch(itemsProvider);
|
||||
final controller = ref.watch(homeControllerProvider);
|
||||
|
||||
widget.item = widget.item.copyWith(isVisible: false);
|
||||
controller.edit(widget.item);
|
||||
|
||||
logger.i('Attempting delete');
|
||||
// ignore: use_build_context_synchronously
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.widgetSettings_deleteButton,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
final controller = ref.watch(homeControllerProvider);
|
||||
|
||||
logger.i('Attempting edit of water widget stuff');
|
||||
// widget.settingEntries.notify();
|
||||
final name = settingEntries.getValue('name') as String;
|
||||
final currentAmount =
|
||||
settingEntries.getValue('currentAmount') as int;
|
||||
final targetGoal =
|
||||
(settingEntries.getValue('targetGoal') as double).round();
|
||||
final button1Amount =
|
||||
settingEntries.getValue('button1Amount') as int;
|
||||
final button2Amount =
|
||||
settingEntries.getValue('button2Amount') as int;
|
||||
|
||||
widget.item = widget.item.copyWith(
|
||||
name: name,
|
||||
current: currentAmount,
|
||||
goal: targetGoal,
|
||||
button1Amount: button1Amount,
|
||||
button2Amount: button2Amount,
|
||||
);
|
||||
controller.edit(widget.item);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.widgetSettings_saveButton,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showPopupCustomAmount() async {
|
||||
final customFieldController = TextEditingController();
|
||||
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
scrollable: true,
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.waterWidget_customAmountMessage,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
content: Column(
|
||||
children: <Widget>[
|
||||
TextField(
|
||||
controller: customFieldController,
|
||||
keyboardType: TextInputType.number,
|
||||
autofocus: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: <Widget>[
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.widgetSettings_cancelButton,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
final result = int.tryParse(customFieldController.text) ?? 0;
|
||||
_addQuantity(result);
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.widgetSettings_saveButton,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OvershootLiquidLinearProgressIndicator extends StatelessWidget {
|
||||
const OvershootLiquidLinearProgressIndicator({
|
||||
required this.current,
|
||||
required this.goal,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final int current;
|
||||
final int goal;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var mainColor = Colors.blue;
|
||||
var backColor = Colors.white;
|
||||
var value = 0.0;
|
||||
if (current < goal) {
|
||||
mainColor = Colors.blue;
|
||||
backColor = Colors.white;
|
||||
value = (current - goal * 0) / goal;
|
||||
} else if (current < goal * 2) {
|
||||
mainColor = Colors.orange;
|
||||
backColor = Colors.blue;
|
||||
value = (current - goal * 1) / goal;
|
||||
} else {
|
||||
mainColor = Colors.red;
|
||||
backColor = Colors.orange;
|
||||
value = (current - goal * 2) / goal;
|
||||
}
|
||||
return LiquidLinearProgressIndicator(
|
||||
value: value,
|
||||
valueColor: AlwaysStoppedAnimation(
|
||||
mainColor,
|
||||
),
|
||||
backgroundColor: backColor,
|
||||
borderColor: Colors.black,
|
||||
borderWidth: 3,
|
||||
borderRadius: 12,
|
||||
direction: Axis.vertical,
|
||||
);
|
||||
}
|
||||
}
|
375
app/lib/function_widgets/widget_settings_menu/setting_entry.dart
Normal file
|
@ -0,0 +1,375 @@
|
|||
import 'package:duration_picker/duration_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:habitrack_app/main.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
abstract class SettingEntry extends StatefulWidget {
|
||||
const SettingEntry({required this.name, super.key});
|
||||
|
||||
final String name;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _SettingEntryState();
|
||||
|
||||
dynamic getValue() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
void setValue(dynamic newValue) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingEntryState extends State<SettingEntry> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Text('Abstract, not implemented');
|
||||
}
|
||||
}
|
||||
|
||||
// ###############################TEXT#########################################
|
||||
class SettingEntryText extends SettingEntry {
|
||||
SettingEntryText({
|
||||
required super.name,
|
||||
this.defaultValue = 'Some Text',
|
||||
super.key,
|
||||
}) {
|
||||
setValue(defaultValue);
|
||||
}
|
||||
|
||||
final TextEditingController valueController = TextEditingController();
|
||||
final String defaultValue;
|
||||
|
||||
@override
|
||||
State<SettingEntryText> createState() => _SettingEntryTextState();
|
||||
|
||||
@override
|
||||
String getValue() {
|
||||
logger.i('GETTING VALUE ${valueController.text}');
|
||||
return valueController.text;
|
||||
}
|
||||
|
||||
@override
|
||||
void setValue(dynamic newValue) {
|
||||
if (newValue is! String) {
|
||||
throw Exception('Value of SettingEntryText can only be a String!');
|
||||
}
|
||||
valueController.text = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingEntryTextState extends State<SettingEntryText> {
|
||||
void monitorTextChange() {
|
||||
final text = widget.valueController.text;
|
||||
logger.i('TEXT: $text');
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Start listening to changes.
|
||||
widget.valueController.addListener(monitorTextChange);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Clean up the controller when the widget is removed from the widget tree.
|
||||
// This also removes the _printLatestValue listener.
|
||||
widget.valueController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
alignment: AlignmentDirectional.bottomStart,
|
||||
margin: const EdgeInsets.only(top: 7.5, bottom: 7.5),
|
||||
|
||||
// color: Colors.black,
|
||||
child: Text(
|
||||
widget.name,
|
||||
textAlign: TextAlign.left,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
keyboardType: TextInputType.text,
|
||||
controller: widget.valueController,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ###############################NUMERIC#######################################
|
||||
class SettingEntryNumeric extends SettingEntry {
|
||||
SettingEntryNumeric({required super.name, int defaultValue = 0, super.key}) {
|
||||
setValue(defaultValue);
|
||||
}
|
||||
|
||||
final TextEditingController valueController = TextEditingController();
|
||||
|
||||
@override
|
||||
State<SettingEntryNumeric> createState() => _SettingEntryNumericState();
|
||||
|
||||
@override
|
||||
int getValue() {
|
||||
return int.parse(valueController.text);
|
||||
}
|
||||
|
||||
@override
|
||||
void setValue(dynamic newValue) {
|
||||
if (newValue is! int) {
|
||||
throw Exception('Value of SettingEntryNumeric can only be an integer!');
|
||||
}
|
||||
valueController.text = newValue.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingEntryNumericState extends State<SettingEntryNumeric> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(widget.name),
|
||||
TextField(
|
||||
keyboardType: TextInputType.number,
|
||||
controller: widget.valueController,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ###############################SLIDER########################################
|
||||
class SettingEntrySlider extends SettingEntry {
|
||||
SettingEntrySlider({
|
||||
required this.topValue,
|
||||
required this.divisions,
|
||||
required super.name,
|
||||
double defaultValue = 0.0,
|
||||
super.key,
|
||||
}) {
|
||||
setValue(defaultValue);
|
||||
}
|
||||
|
||||
final double topValue;
|
||||
final int divisions;
|
||||
final value = DoubleSaver();
|
||||
|
||||
@override
|
||||
State<SettingEntrySlider> createState() => _SettingEntrySliderState();
|
||||
|
||||
@override
|
||||
double getValue() {
|
||||
return value.v;
|
||||
}
|
||||
|
||||
@override
|
||||
void setValue(dynamic newValue) {
|
||||
if (newValue is! double) {
|
||||
throw Exception('Value of SettingEntrySlider can only be a Double!');
|
||||
}
|
||||
value.v = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
class DoubleSaver {
|
||||
double v = 0;
|
||||
}
|
||||
|
||||
class _SettingEntrySliderState extends State<SettingEntrySlider> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
//Expanded(flex: 2, child: Text(widget.name)),
|
||||
Text(widget.name),
|
||||
Text(' ${widget.getValue()}'),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Text('0 '),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
value: widget.getValue(),
|
||||
divisions: widget.divisions,
|
||||
max: widget.topValue,
|
||||
onChanged: sliderChange,
|
||||
),
|
||||
),
|
||||
Text(' ${widget.topValue}'),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void sliderChange(double newValue) {
|
||||
setState(() {
|
||||
widget.setValue(newValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// #############################DURATION#######################################
|
||||
|
||||
class IntSaver {
|
||||
IntSaver({this.v = 30});
|
||||
int v;
|
||||
}
|
||||
|
||||
class SettingEntryDuration extends SettingEntry {
|
||||
SettingEntryDuration({
|
||||
required super.name,
|
||||
required this.defaultValue,
|
||||
super.key,
|
||||
}) {
|
||||
val = IntSaver(v: defaultValue);
|
||||
}
|
||||
final int defaultValue;
|
||||
late final IntSaver val;
|
||||
|
||||
@override
|
||||
State<SettingEntryDuration> createState() => _SettingEntryDurationState();
|
||||
|
||||
@override
|
||||
int getValue() {
|
||||
return val.v;
|
||||
}
|
||||
|
||||
@override
|
||||
void setValue(dynamic newValue) {
|
||||
if (newValue is! int) {
|
||||
throw Exception('Value of SettingEntryDuration can only be a Double!');
|
||||
}
|
||||
val.v = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingEntryDurationState extends State<SettingEntryDuration> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textCurrent =
|
||||
AppLocalizations.of(context)!.widgetSettings_durationPickerCurrent;
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
alignment: AlignmentDirectional.bottomStart,
|
||||
margin: const EdgeInsets.only(top: 7.5, bottom: 7.5),
|
||||
child: Text(
|
||||
'$textCurrent ${widget.getValue()}m',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: () async {
|
||||
var resultingDuration = await showDurationPicker(
|
||||
context: context,
|
||||
initialTime: Duration(minutes: widget.val.v),
|
||||
);
|
||||
resultingDuration ??= Duration(minutes: widget.defaultValue);
|
||||
// ignore: use_build_context_synchronously
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Chose duration: ${resultingDuration.inMinutes}'),
|
||||
),
|
||||
);
|
||||
widget.setValue(resultingDuration.inMinutes);
|
||||
setState(() {});
|
||||
},
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.widgetSettings_durationPickerButton,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ###############################DATE#########################################
|
||||
class DateSaver {
|
||||
DateTime v = DateTime.now();
|
||||
}
|
||||
|
||||
class SettingEntryDate extends SettingEntry {
|
||||
SettingEntryDate({required super.name, DateTime? defaultValue, super.key}) {
|
||||
this.defaultValue = defaultValue ?? DateTime.now();
|
||||
setValue(defaultValue);
|
||||
}
|
||||
|
||||
final date = DateSaver();
|
||||
|
||||
late final DateTime defaultValue;
|
||||
|
||||
@override
|
||||
State<SettingEntryDate> createState() => _SettingEntryDateState();
|
||||
|
||||
@override
|
||||
DateTime getValue() {
|
||||
return date.v;
|
||||
}
|
||||
|
||||
@override
|
||||
void setValue(dynamic newValue) {
|
||||
if (newValue is! DateTime) {
|
||||
throw Exception('Value of SettingEntryNumeric can only be a DateTime!');
|
||||
}
|
||||
date.v = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingEntryDateState extends State<SettingEntryDate> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final dateFormat = DateFormat('dd. MMMM');
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
'${widget.name}: ${dateFormat.format(widget.date.v)} ',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: datePicker,
|
||||
child: const Text('Choose a date'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> datePicker() async {
|
||||
final pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: widget.date.v, //get today's date
|
||||
firstDate: DateTime.now(),
|
||||
//DateTime.now() - not to allow to choose before today.
|
||||
lastDate: DateTime(2101),
|
||||
);
|
||||
final cleanDate = pickedDate ?? widget.date.v;
|
||||
widget.setValue(cleanDate);
|
||||
setState(() {});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart';
|
||||
|
||||
class WidgetSettingsData {
|
||||
WidgetSettingsData({required this.entries});
|
||||
|
||||
final Map<State, void Function(void Function())> listeners = {};
|
||||
final Map<String, SettingEntry> entries;
|
||||
|
||||
void addListener(State toAdd, void Function(void Function()) updateCall) {
|
||||
listeners[toAdd] = updateCall;
|
||||
}
|
||||
|
||||
void removeListener(State toRemove) {
|
||||
listeners.remove(toRemove);
|
||||
}
|
||||
|
||||
void notify() {
|
||||
for (final listener in listeners.keys) {
|
||||
listeners[listener]!(() => ());
|
||||
}
|
||||
}
|
||||
|
||||
dynamic getValue(String key) {
|
||||
return entries[key]?.getValue();
|
||||
}
|
||||
|
||||
void setValue(String key, dynamic value) {
|
||||
entries[key]?.setValue(value);
|
||||
notify();
|
||||
}
|
||||
|
||||
List<SettingEntry> asList() {
|
||||
return entries.values.toList();
|
||||
}
|
||||
}
|
||||
|
||||
class WidgetSettings extends StatefulWidget {
|
||||
const WidgetSettings({
|
||||
required this.entries,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final WidgetSettingsData entries;
|
||||
|
||||
@override
|
||||
State<WidgetSettings> createState() => _WidgetSettingsState();
|
||||
}
|
||||
|
||||
class _WidgetSettingsState extends State<WidgetSettings> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
iconTheme: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,),
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.settingsHeader,
|
||||
textScaler: const TextScaler.linear(1.5),
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: ColoredBox(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
child: ListView(
|
||||
children: widget.entries.asList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
42
app/lib/infrastructure/bottom_navigation.dart
Normal file
|
@ -0,0 +1,42 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class ScaffoldWithBottomNavigationBar extends StatelessWidget {
|
||||
const ScaffoldWithBottomNavigationBar({
|
||||
required this.navigationShell,
|
||||
Key? key,
|
||||
}) : super(key: key ?? const ValueKey<String>('ScaffoldWithNavBar'));
|
||||
|
||||
final StatefulNavigationShell navigationShell;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: SafeArea(child: navigationShell),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
fixedColor: Theme.of(context).colorScheme.onPrimary,
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.list),
|
||||
label: 'Widget Wall',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.dashboard),
|
||||
label: 'Dashboard',
|
||||
),
|
||||
],
|
||||
currentIndex: navigationShell.currentIndex,
|
||||
onTap: (int index) => _onTap(context, index),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onTap(BuildContext context, int index) {
|
||||
navigationShell.goBranch(
|
||||
index,
|
||||
initialLocation: index == navigationShell.currentIndex,
|
||||
);
|
||||
}
|
||||
}
|
57
app/lib/infrastructure/routing.dart
Normal file
|
@ -0,0 +1,57 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:habitrack_app/infrastructure/bottom_navigation.dart';
|
||||
import 'package:habitrack_app/pages/dashboard_page.dart';
|
||||
import 'package:habitrack_app/pages/widget_page.dart';
|
||||
|
||||
// navigators, root and each destination of bottom navigation bar
|
||||
final _rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
final _shellNavigatorWidgetWallKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'widgetWall');
|
||||
|
||||
final _shellNavigatorDashboardKey =
|
||||
GlobalKey<NavigatorState>(debugLabel: 'dashboard');
|
||||
|
||||
const String _widgetWallPath = '/widgetWall';
|
||||
const String _dashboardPath = '/dashboard';
|
||||
|
||||
final goRouter = GoRouter(
|
||||
initialLocation: _widgetWallPath,
|
||||
navigatorKey: _rootNavigatorKey,
|
||||
debugLogDiagnostics: true,
|
||||
routes: [
|
||||
StatefulShellRoute.indexedStack(
|
||||
builder: (context, state, navigationShell) {
|
||||
return ScaffoldWithBottomNavigationBar(
|
||||
navigationShell: navigationShell,
|
||||
);
|
||||
},
|
||||
branches: [
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _shellNavigatorWidgetWallKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: _widgetWallPath,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: WidgetPage(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: _shellNavigatorDashboardKey,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: _dashboardPath,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: DashboardPage(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
27
app/lib/infrastructure/widget_wall/add_widget_button.dart
Normal file
|
@ -0,0 +1,27 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:habitrack_app/infrastructure/widget_wall/add_widget_menu.dart';
|
||||
|
||||
class AddWidgetButton extends StatelessWidget {
|
||||
const AddWidgetButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: () => {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const AddWidgetMenu(),
|
||||
),
|
||||
),
|
||||
},
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
150
app/lib/infrastructure/widget_wall/add_widget_menu.dart
Normal file
|
@ -0,0 +1,150 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart';
|
||||
import 'package:habitrack_app/main.dart';
|
||||
import 'package:habitrack_app/sembast/hydration.dart';
|
||||
import 'package:habitrack_app/sembast/tasks_list.dart';
|
||||
import 'package:habitrack_app/sembast/timer.dart';
|
||||
|
||||
//Add Widget to List- Button Class ######################################
|
||||
class _AddWidgetToList extends ConsumerWidget {
|
||||
const _AddWidgetToList({
|
||||
required this.toAdd,
|
||||
required this.buttonText,
|
||||
required this.iconData,
|
||||
});
|
||||
|
||||
final dynamic toAdd;
|
||||
final String buttonText;
|
||||
final IconData iconData;
|
||||
|
||||
void _buttonFunc(BuildContext context, WidgetRef ref) {
|
||||
//ref.read(widgetListNotifierProvider.notifier).addWidget(toAdd());
|
||||
//ref.read(homeControllerProvider).add()
|
||||
ref.watch(homeControllerProvider).add(toAdd);
|
||||
|
||||
Navigator.pop(context);
|
||||
logger.i('Button Func Called');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0x00000000).withOpacity(0.25),
|
||||
spreadRadius: 1,
|
||||
blurRadius: 2,
|
||||
),
|
||||
],
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
width: 300,
|
||||
child: TextButton(
|
||||
onPressed: () => {_buttonFunc(context, ref)},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
iconData,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
Text(
|
||||
buttonText,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// AddWidgetMenu ##########################################################
|
||||
class AddWidgetMenu extends StatelessWidget {
|
||||
const AddWidgetMenu({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
//AddWidgetToList - Button-Instances #######################################
|
||||
final addWaterWidget = _AddWidgetToList(
|
||||
toAdd: Hydration(
|
||||
createdOn: DateTime.now().toString(),
|
||||
completedOn: '',
|
||||
isVisible: true,
|
||||
widgetType: 'Hydration',
|
||||
name: AppLocalizations.of(context)!.waterWidget_defaultName,
|
||||
button1Amount: 100,
|
||||
button2Amount: 250,
|
||||
goal: 2500,
|
||||
current: 0,
|
||||
isExpanded: false,
|
||||
),
|
||||
buttonText: AppLocalizations.of(context)!.addWidget_water,
|
||||
iconData: Icons.local_drink,
|
||||
);
|
||||
|
||||
final addCompoundTimerWidget = _AddWidgetToList(
|
||||
toAdd: TimerItem(
|
||||
widgetType: 'Timer',
|
||||
name: AppLocalizations.of(context)!.timerWidget_defaultName,
|
||||
current: 0,
|
||||
goal: 90,
|
||||
isExpanded: false,
|
||||
createdOn: DateTime.now().toString(),
|
||||
completedOn: '',
|
||||
isVisible: true,
|
||||
state: 'initial',
|
||||
),
|
||||
buttonText: AppLocalizations.of(context)!.addWidget_timer,
|
||||
iconData: Icons.timer,
|
||||
);
|
||||
final addTaskWidget = _AddWidgetToList(
|
||||
toAdd: TasksItem(
|
||||
isVisible: true,
|
||||
widgetType: 'TODO',
|
||||
name: AppLocalizations.of(context)!.tasksWidget_defaultName,
|
||||
isExpanded: false,
|
||||
taskList: [],
|
||||
completedTaskList: [],
|
||||
),
|
||||
buttonText: AppLocalizations.of(context)!.addWidget_tasks,
|
||||
iconData: Icons.task,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
iconTheme: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.addWidgetHeader,
|
||||
textScaler: const TextScaler.linear(1.5),
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
body: Container(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
addWaterWidget,
|
||||
addTaskWidget,
|
||||
addCompoundTimerWidget,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
109
app/lib/infrastructure/widget_wall/graph_widget.dart
Normal file
|
@ -0,0 +1,109 @@
|
|||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class DataPoint {
|
||||
DataPoint({required this.date, required this.progress});
|
||||
DateTime date;
|
||||
double progress;
|
||||
}
|
||||
|
||||
class GraphWidget extends ConsumerStatefulWidget {
|
||||
GraphWidget({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _GraphWidgetState();
|
||||
|
||||
final entries = <DataPoint>[
|
||||
DataPoint(date: DateTime(2024, 7, 4), progress: 1),
|
||||
DataPoint(date: DateTime(2024, 7, 5), progress: 0.5),
|
||||
DataPoint(date: DateTime(2024, 7, 6), progress: 1.25),
|
||||
DataPoint(date: DateTime(2024, 7, 7), progress: 0.75),
|
||||
];
|
||||
}
|
||||
|
||||
class _GraphWidgetState extends ConsumerState<GraphWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final firstDate = widget.entries[0].date;
|
||||
final flSpots = <FlSpot>[];
|
||||
for (final dataPoint in widget.entries) {
|
||||
final xValue = _daysBetween(firstDate, dataPoint.date).toDouble();
|
||||
final lcbd = FlSpot(xValue, dataPoint.progress);
|
||||
flSpots.add(lcbd);
|
||||
}
|
||||
final df = DateFormat('dd. MMMM');
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.85,
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
minY: 0,
|
||||
maxY: 1.5,
|
||||
lineBarsData: [
|
||||
LineChartBarData(spots: flSpots, isCurved: true),
|
||||
],
|
||||
gridData: const FlGridData(show: false),
|
||||
extraLinesData: ExtraLinesData(
|
||||
horizontalLines: [
|
||||
HorizontalLine(
|
||||
y: 1,
|
||||
color: Colors.red,
|
||||
),
|
||||
],
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
reservedSize: 30,
|
||||
interval: 20,
|
||||
),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
interval: 20,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 30,
|
||||
getTitlesWidget: (value, meta) => Text(value.toString()),
|
||||
interval: 0.25,
|
||||
),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
getTitlesWidget: (value, meta) => Text(
|
||||
df.format(
|
||||
DateTime(
|
||||
firstDate.year,
|
||||
firstDate.month,
|
||||
firstDate.day,
|
||||
).add(Duration(days: value.round())),
|
||||
),
|
||||
),
|
||||
showTitles: true,
|
||||
interval: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Text('You worked for X hours this past week'),
|
||||
const Text('Out of a total of Y hours planned'),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
int _daysBetween(DateTime from, DateTime to) {
|
||||
final d1 = DateTime(from.year, from.month, from.day);
|
||||
final d2 = DateTime(to.year, to.month, to.day);
|
||||
return (d2.difference(d1).inHours / 24).round();
|
||||
}
|
||||
}
|
27
app/lib/infrastructure/widget_wall/items_controller.dart
Normal file
|
@ -0,0 +1,27 @@
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:habitrack_app/sembast/item_repository.dart';
|
||||
import 'package:habitrack_app/sembast/sembast_item_repository.dart';
|
||||
|
||||
final homeControllerProvider = Provider(
|
||||
(ref) => HomeController(
|
||||
itemRepository: ref.watch(itemRepositoryProvider),
|
||||
),
|
||||
);
|
||||
|
||||
class HomeController {
|
||||
HomeController({required this.itemRepository});
|
||||
|
||||
final ItemRepository itemRepository;
|
||||
|
||||
Future<void> delete(int id) async {
|
||||
await itemRepository.deleteItem(id);
|
||||
}
|
||||
|
||||
Future<void> edit(dynamic item) async {
|
||||
await itemRepository.updateItem(item);
|
||||
}
|
||||
|
||||
Future<void> add(dynamic newItem) async {
|
||||
await itemRepository.insertItem(newItem);
|
||||
}
|
||||
}
|
6
app/lib/infrastructure/widget_wall/items_state.dart
Normal file
|
@ -0,0 +1,6 @@
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:habitrack_app/sembast/sembast_item_repository.dart';
|
||||
|
||||
final itemsProvider = StreamProvider(
|
||||
(ref) => ref.watch(itemRepositoryProvider).getAllItemsStream(),
|
||||
);
|
111
app/lib/infrastructure/widget_wall/widget_wall.dart
Normal file
|
@ -0,0 +1,111 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:habitrack_app/function_widgets/compound_widgets/compound_timer_widget.dart';
|
||||
import 'package:habitrack_app/function_widgets/compound_widgets/compound_widget_tasks.dart';
|
||||
import 'package:habitrack_app/function_widgets/compound_widgets/compound_widget_water.dart';
|
||||
import 'package:habitrack_app/infrastructure/widget_wall/add_widget_button.dart';
|
||||
import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart';
|
||||
import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart';
|
||||
import 'package:habitrack_app/main.dart';
|
||||
import 'package:habitrack_app/sembast/hydration.dart';
|
||||
import 'package:habitrack_app/sembast/tasks_list.dart';
|
||||
import 'package:habitrack_app/sembast/timer.dart';
|
||||
|
||||
/// Displays detailed information about a SampleItem.
|
||||
|
||||
class WidgetWall extends ConsumerStatefulWidget {
|
||||
const WidgetWall({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<WidgetWall> createState() => _WidgetWallState();
|
||||
}
|
||||
|
||||
class _WidgetWallState extends ConsumerState<WidgetWall> {
|
||||
Future<List<Widget>> buildList() async {
|
||||
ref.watch(homeControllerProvider);
|
||||
//var itemCount = 0;
|
||||
|
||||
final items = ref.watch(itemsProvider);
|
||||
|
||||
// ignore: unused_local_variable
|
||||
final val = items.value;
|
||||
|
||||
return <Widget>[];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// this.buildList();
|
||||
final controller = ref.watch(itemsProvider);
|
||||
switch (controller) {
|
||||
case AsyncError(:final error):
|
||||
return Text('Error: $error');
|
||||
case AsyncData(:final value):
|
||||
final allItems = value;
|
||||
final items = <dynamic>[];
|
||||
for (var i = 0; i < allItems.length; i++) {
|
||||
// ignore: avoid_dynamic_calls
|
||||
if (allItems.elementAt(i).isVisible == true) {
|
||||
logger.i('INSERTING VISIBLE ITEM');
|
||||
items.add(allItems.elementAt(i));
|
||||
}
|
||||
}
|
||||
final itemWidgets = <Widget>[];
|
||||
final itemCount = items.length;
|
||||
for (var i = 0; i < itemCount; i++) {
|
||||
final item = items.elementAt(i);
|
||||
if (item is Hydration && item.isVisible) {
|
||||
final itemwidget = CompoundWidgetWater(item: item);
|
||||
itemWidgets.insert(i, itemwidget);
|
||||
} else if (item is TimerItem && item.isVisible) {
|
||||
if (item.isVisible == true) {
|
||||
logger.i('VISIBLE');
|
||||
final itemwidget = CompoundWidgetTimer(item: item);
|
||||
itemWidgets.insert(i, itemwidget);
|
||||
} else {
|
||||
logger.i('IS NOT VISIBLE');
|
||||
}
|
||||
} else if (item is TasksItem && item.isVisible) {
|
||||
final itemwidget = CompoundWidgetTasks(item: item);
|
||||
itemWidgets.insert(i, itemwidget);
|
||||
}
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
iconTheme: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
textScaler: const TextScaler.linear(2),
|
||||
'Habitrack ',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSecondary,
|
||||
// fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
const Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: AddWidgetButton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: ColoredBox(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(8),
|
||||
children: itemWidgets,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
default:
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
}
|
||||
}
|
59
app/lib/l10n/app_de.arb
Normal file
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
|
||||
|
||||
"settingsHeader": "Einstellungen",
|
||||
|
||||
"widgetSettings_durationPickerCurrent": "Derzeitige Laufzeit:",
|
||||
"widgetSettings_durationPickerButton": "Laufzeit auswählen",
|
||||
|
||||
"addWidgetHeader": "Widget Hinzufügen",
|
||||
"addWidget_water": "Wasser-Buddy hinzufügen",
|
||||
"addWidget_timer": "Fokus-Timer hinzufügen",
|
||||
"addWidget_tasks": "Tasks Widget hinzufügen",
|
||||
|
||||
"widgetSettings_name": "Name des Widgets",
|
||||
"widgetSettings_saveButton": "Speichern",
|
||||
"widgetSettings_deleteButton": "Löschen",
|
||||
"widgetSettings_cancelButton": "Abbrechen",
|
||||
|
||||
"waterWidget_defaultName": "Trink-Buddy",
|
||||
"waterWidgetSettings_button1": "Button 1 in ml",
|
||||
"waterWidgetSettings_button2": "Button 2 in ml",
|
||||
"waterWidgetSettings_goal": "Tagesziel",
|
||||
"waterWidgetSettings_current": "Derzeit getrunken",
|
||||
|
||||
"waterWidget_customAmountButton": "+ Beliebig",
|
||||
"waterWidget_customAmountMessage": "Geben sie die Geünschte Wassermenge ein:",
|
||||
|
||||
"tasksWidget_defaultName": "Tasks Widget",
|
||||
"tasksWidget_addTaskButtonLabel": "Aufgabe Hinzufügen",
|
||||
|
||||
"tasksWidget_editTask": "Aufgabe bearbeiten",
|
||||
|
||||
"taskDefaultName": "Neue Aufgabe",
|
||||
"taskSettings_name": "Aufgabenname",
|
||||
"taskSettings_due": "Fällig am",
|
||||
"taskSettings_duePicker": "Fälligkeitsdatum auswählen",
|
||||
|
||||
"tasksWidget_overdue": "Überfällig!",
|
||||
|
||||
"tasksWidget_deleteDone": "Erledigte Aufgaben löschen",
|
||||
"tasksWidget_rescheduleOverdue": "Überfällige Aufgaben neu planen",
|
||||
|
||||
"timerWidget_defaultName": "Fokus-Timer",
|
||||
"timerWidget_buttonStart": "Starten",
|
||||
"timerWidget_buttonPause": "Anhalten",
|
||||
"timerWidget_buttonContinue": "Weiter",
|
||||
"timerWidget_buttonReset": "Zurücksetzen",
|
||||
|
||||
"timerWidgetSettings_duration": "Tägliches Ziel",
|
||||
|
||||
"timerWidget_current": "Jetzt",
|
||||
"timerWidget_goal": "Ziel",
|
||||
|
||||
"timerWidget_pausedForEdit": "Halte den Timer an, um ihn bearbeiten zu können!"
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
64
app/lib/l10n/app_en.arb
Normal file
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
|
||||
|
||||
|
||||
"settingsHeader": "Settings",
|
||||
|
||||
"widgetSettings_durationPickerCurrent": "Current Duration:",
|
||||
"widgetSettings_durationPickerButton": "Choose Duration",
|
||||
|
||||
"addWidgetHeader": "Widget Picker",
|
||||
"addWidget_water": "Add Hydration Tracker",
|
||||
"addWidget_timer": "Add Focus Timer",
|
||||
"addWidget_tasks": "Add Tasks Widget",
|
||||
|
||||
"widgetSettings_name": "Widget Name",
|
||||
"widgetSettings_saveButton": "Confirm",
|
||||
"widgetSettings_deleteButton": "Delete",
|
||||
"widgetSettings_cancelButton": "Cancel",
|
||||
|
||||
"waterWidget_defaultName": "Hydration Tracker",
|
||||
"waterWidgetSettings_button1": "Button 1 in ml",
|
||||
"waterWidgetSettings_button2": "Button 2 in ml",
|
||||
"waterWidgetSettings_goal": "Daily Goal",
|
||||
"waterWidgetSettings_current": "Current Amount",
|
||||
|
||||
"waterWidget_customAmountButton": "+ custom",
|
||||
"waterWidget_customAmountMessage": "Enter an amount as desired:",
|
||||
|
||||
|
||||
"tasksWidget_defaultName": "Tasks Widget",
|
||||
"tasksWidget_addTaskButtonLabel": "Add Task",
|
||||
|
||||
"tasksWidget_editTask": "Edit Task",
|
||||
|
||||
"taskDefaultName": "Examplar Task",
|
||||
"taskSettings_name": "Task Name",
|
||||
"taskSettings_due": "Due Date",
|
||||
"taskSettings_duePicker": "Choose a due date",
|
||||
|
||||
"tasksWidget_overdue": "Overdue!",
|
||||
|
||||
"tasksWidget_deleteDone": "Delete completed tasks",
|
||||
"tasksWidget_rescheduleOverdue": "Reschedule overdue tasks",
|
||||
|
||||
|
||||
"timerWidget_defaultName": "Focus Timer",
|
||||
"timerWidget_buttonStart": "Start",
|
||||
"timerWidget_buttonPause": "Stop",
|
||||
"timerWidget_buttonContinue": "Continue",
|
||||
"timerWidget_buttonReset": "Reset",
|
||||
|
||||
"timerWidgetSettings_duration": "Daily Goal",
|
||||
|
||||
"timerWidget_current": "Current",
|
||||
"timerWidget_goal": "Goal",
|
||||
|
||||
"timerWidget_pausedForEdit": "Timer must be paused before editing!"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
86
app/lib/main.dart
Normal file
|
@ -0,0 +1,86 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_background/flutter_background.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:habitrack_app/infrastructure/routing.dart';
|
||||
import 'package:habitrack_app/sembast/global_providers.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sembast/sembast_io.dart';
|
||||
import 'package:sembast_web/sembast_web.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
if (kIsWeb) {
|
||||
final factory = databaseFactoryWeb;
|
||||
final db = await factory.openDatabase('test');
|
||||
runApp(
|
||||
ProviderScope(
|
||||
overrides: [databaseProvider.overrideWithValue(db)],
|
||||
child: const MainApp(),
|
||||
),
|
||||
);
|
||||
// running on the web!
|
||||
} else {
|
||||
final appPath = await getApplicationDocumentsDirectory();
|
||||
|
||||
appPath.createSync(recursive: true);
|
||||
final dbPath = join(appPath.path, 'widgets.db');
|
||||
final database = await databaseFactoryIo.openDatabase(dbPath);
|
||||
const androidConfig = FlutterBackgroundAndroidConfig(
|
||||
notificationTitle: 'flutter_background example app',
|
||||
notificationText:
|
||||
// ignore: lines_longer_than_80_chars
|
||||
'Background notification for keeping the example app running in the background',
|
||||
notificationIcon: AndroidResource(
|
||||
name: 'background_icon',
|
||||
// ignore: avoid_redundant_argument_values
|
||||
defType: 'drawable',
|
||||
), // Default is ic_launcher from folder mipmap
|
||||
);
|
||||
|
||||
await FlutterBackground.initialize(androidConfig: androidConfig);
|
||||
await FlutterBackground.enableBackgroundExecution();
|
||||
|
||||
runApp(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
databaseProvider.overrideWithValue(database),
|
||||
],
|
||||
child: const MainApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Logger logger = Logger();
|
||||
|
||||
class MainApp extends StatelessWidget {
|
||||
const MainApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(
|
||||
title: 'Habitrack',
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.purple,
|
||||
),
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.amber,
|
||||
),
|
||||
),
|
||||
),
|
||||
routerConfig: goRouter,
|
||||
);
|
||||
}
|
||||
}
|
76
app/lib/pages/dashboard_hydration_subpage.dart
Normal file
|
@ -0,0 +1,76 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:habitrack_app/pages/hydration_graph_widget.dart';
|
||||
|
||||
class SubpageHydrationButton extends StatelessWidget {
|
||||
const SubpageHydrationButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 300,
|
||||
margin: const EdgeInsets.only(top: 20, bottom: 7.5),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
minimumSize: const Size(10, 70),
|
||||
),
|
||||
onPressed: () => {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const DashboardHydrationSubpage(),
|
||||
),
|
||||
),
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.local_drink,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
Text(
|
||||
' Hydration Widgets',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DashboardHydrationSubpage extends StatefulWidget {
|
||||
const DashboardHydrationSubpage({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _DashboardHydrationSubpageState();
|
||||
}
|
||||
|
||||
class _DashboardHydrationSubpageState extends State<DashboardHydrationSubpage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
appBar: AppBar(
|
||||
iconTheme: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: Text(
|
||||
'Statistics: Hydration Widgets',
|
||||
textScaler: const TextScaler.linear(1.2),
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: const HydrationGraphWidget(),
|
||||
);
|
||||
}
|
||||
}
|
47
app/lib/pages/dashboard_page.dart
Normal file
|
@ -0,0 +1,47 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:habitrack_app/pages/dashboard_hydration_subpage.dart';
|
||||
import 'package:habitrack_app/pages/dashboard_task_subpage.dart';
|
||||
import 'package:habitrack_app/pages/dashboard_timer_subpage.dart';
|
||||
import 'package:habitrack_app/pages/reset_subpage.dart';
|
||||
|
||||
class DashboardPage extends ConsumerStatefulWidget {
|
||||
const DashboardPage({super.key});
|
||||
@override
|
||||
ConsumerState<DashboardPage> createState() => _DashboardPageState();
|
||||
}
|
||||
|
||||
class _DashboardPageState extends ConsumerState<DashboardPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
//final items = ref.watch(itemsProvider);
|
||||
|
||||
//final len = items.value!.length;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
'Dashboard',
|
||||
textScaler: const TextScaler.linear(1.4),
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSecondary,
|
||||
),
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
body: ColoredBox(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: const Column(
|
||||
children: [
|
||||
SubpageHydrationButton(),
|
||||
SubpageTaskButton(),
|
||||
SubpageTimerButton(),
|
||||
ResetSubpageButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
76
app/lib/pages/dashboard_task_subpage.dart
Normal file
|
@ -0,0 +1,76 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:habitrack_app/pages/tasks_graph_widget.dart';
|
||||
|
||||
class SubpageTaskButton extends StatelessWidget {
|
||||
const SubpageTaskButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 300,
|
||||
margin: const EdgeInsets.only(top: 7.5, bottom: 7.5),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
minimumSize: const Size(10, 70),
|
||||
),
|
||||
onPressed: () => {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const DashboardTaskSubpage(),
|
||||
),
|
||||
),
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.task,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
Text(
|
||||
' Task Widgets',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DashboardTaskSubpage extends StatefulWidget {
|
||||
const DashboardTaskSubpage({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _DashboardTaskSubpageState();
|
||||
}
|
||||
|
||||
class _DashboardTaskSubpageState extends State<DashboardTaskSubpage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
appBar: AppBar(
|
||||
iconTheme: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: Text(
|
||||
'Statistics: Tasks Widgets',
|
||||
textScaler: const TextScaler.linear(1.2),
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: TasksGraphWidget(),
|
||||
);
|
||||
}
|
||||
}
|
76
app/lib/pages/dashboard_timer_subpage.dart
Normal file
|
@ -0,0 +1,76 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:habitrack_app/pages/timer_graph_widget.dart';
|
||||
|
||||
class SubpageTimerButton extends StatelessWidget {
|
||||
const SubpageTimerButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 300,
|
||||
margin: const EdgeInsets.only(top: 7.5, bottom: 7.5),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
minimumSize: const Size(10, 70),
|
||||
),
|
||||
onPressed: () => {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const DashboardTimerSubpage(),
|
||||
),
|
||||
),
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.timer,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
Text(
|
||||
' Timer Widgets',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DashboardTimerSubpage extends StatefulWidget {
|
||||
const DashboardTimerSubpage({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _DashboardTimerSubpageState();
|
||||
}
|
||||
|
||||
class _DashboardTimerSubpageState extends State<DashboardTimerSubpage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
appBar: AppBar(
|
||||
iconTheme: IconThemeData(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondary,
|
||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||
title: Text(
|
||||
'Statistics: Timer Widgets',
|
||||
textScaler: const TextScaler.linear(1.2),
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
body: TimerGraphWidget(),
|
||||
);
|
||||
}
|
||||
}
|
478
app/lib/pages/hydration_graph_widget.dart
Normal file
|
@ -0,0 +1,478 @@
|
|||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart';
|
||||
import 'package:habitrack_app/main.dart';
|
||||
import 'package:habitrack_app/sembast/hydration.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class DataPoint {
|
||||
DataPoint({required this.date, required this.progress});
|
||||
DateTime date;
|
||||
double progress;
|
||||
}
|
||||
|
||||
class HydrationGraphWidget extends ConsumerStatefulWidget {
|
||||
const HydrationGraphWidget({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() =>
|
||||
_TasksGraphWidgetState();
|
||||
}
|
||||
|
||||
class _TasksGraphWidgetState extends ConsumerState<HydrationGraphWidget> {
|
||||
void _buildList(List<dynamic> value) {
|
||||
final items = value;
|
||||
_thisWeekCompleted = 0;
|
||||
_thisWeekPlanned = 0;
|
||||
_maxAmount = 0;
|
||||
|
||||
setState(() {
|
||||
_todayCompleted = 0;
|
||||
_todayPlanned = 0;
|
||||
_weeklyPlannedEntries = [];
|
||||
_weeklyWorkedEntries = [];
|
||||
for (var i = 0; i <= 6; i++) {
|
||||
final itemToInsert = DataPoint(
|
||||
date: _latestDate.subtract(Duration(days: 6 - i)),
|
||||
progress: 0,
|
||||
);
|
||||
|
||||
_weeklyPlannedEntries.add(itemToInsert);
|
||||
}
|
||||
|
||||
for (var i = 0; i <= 6; i++) {
|
||||
final itemToInsert = DataPoint(
|
||||
date: _latestDate.subtract(Duration(days: 6 - i)),
|
||||
progress: 0,
|
||||
);
|
||||
_weeklyWorkedEntries.add(itemToInsert);
|
||||
}
|
||||
});
|
||||
_thisWeekPlanned = 0;
|
||||
|
||||
for (final item in items) {
|
||||
if (_selectedValue == 'weekly') {
|
||||
var alreadyAdded = false;
|
||||
for (var i = 0; i <= 6; i++) {
|
||||
DateTime parsedDate;
|
||||
|
||||
if (item is Hydration) {
|
||||
if (item.current / item.goal >= 1 && item.completedOn != '') {
|
||||
//item is completed
|
||||
parsedDate = DateTime.parse(item.completedOn);
|
||||
} else {
|
||||
parsedDate = DateTime.parse(item.createdOn);
|
||||
logger.i('GOAL: $item.goal');
|
||||
}
|
||||
|
||||
logger
|
||||
.i('BEFORE $i days ago and AFTER ${i + 1} days ago, element at '
|
||||
'index ${6 - i} will be updated.');
|
||||
|
||||
if (parsedDate.isBefore(_latestDate.subtract(Duration(days: i))) &&
|
||||
parsedDate
|
||||
.isAfter(_latestDate.subtract(Duration(days: i + 1)))) {
|
||||
logger.i('LOOPING');
|
||||
if (!alreadyAdded) {
|
||||
alreadyAdded = true;
|
||||
_thisWeekCompleted += item.current / 1000;
|
||||
|
||||
_thisWeekPlanned += item.goal / 1000;
|
||||
}
|
||||
|
||||
if (item.goal > _maxAmount) {
|
||||
_maxAmount = (item.goal.toDouble() / 1000).ceilToDouble();
|
||||
}
|
||||
if (item.current > _maxAmount && item.current > item.goal) {
|
||||
_maxAmount = (item.current.toDouble() / 1000).ceilToDouble();
|
||||
}
|
||||
// Update maxAmount
|
||||
setState(() {
|
||||
_weeklyPlannedEntries.elementAt(6 - i).progress =
|
||||
item.goal.toDouble() / 1000;
|
||||
_weeklyWorkedEntries.elementAt(6 - i).progress =
|
||||
item.current.toDouble() / 1000;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (_selectedValue == 'daily' && item is Hydration) {
|
||||
logger.i('DATES');
|
||||
final parsedDate = DateTime.parse(item.createdOn);
|
||||
logger.i('LATEST DATE: $_latestDate');
|
||||
|
||||
final oneDayAgo = _latestDate.subtract(const Duration(days: 1));
|
||||
|
||||
if (parsedDate.isBefore(oneDayAgo)) {
|
||||
logger.i('More than a day old');
|
||||
} else if (parsedDate.isAfter(oneDayAgo) &&
|
||||
parsedDate.isBefore(_latestDate)) {
|
||||
logger.i('TOday');
|
||||
_todayCompleted = item.current / 1000;
|
||||
_todayPlanned = item.goal / 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showPreviousWeek() {
|
||||
setState(() {
|
||||
_todayPlanned = 0;
|
||||
_todayCompleted = 0;
|
||||
_thisWeekPlanned = 0;
|
||||
_thisWeekCompleted = 0;
|
||||
});
|
||||
|
||||
if (_selectedValue == 'weekly') {
|
||||
_latestDate = _latestDate.subtract(const Duration(days: 7));
|
||||
} else if (_selectedValue == 'daily') {
|
||||
logger.i('HMMM');
|
||||
_latestDate = _latestDate.subtract(const Duration(days: 1));
|
||||
}
|
||||
final items = ref.watch(itemsProvider);
|
||||
|
||||
switch (items) {
|
||||
case AsyncError(:final error):
|
||||
logger.i('Error: $error');
|
||||
case AsyncData(:final value):
|
||||
final allItems = value;
|
||||
|
||||
_buildList(allItems);
|
||||
default:
|
||||
logger.i('Hmmm, how can we help?');
|
||||
} // get current date
|
||||
// show past 7 days starting 14 days ago
|
||||
}
|
||||
|
||||
void _showNextWeek() {
|
||||
if (_selectedValue == 'weekly') {
|
||||
_latestDate = _latestDate.add(const Duration(days: 7));
|
||||
} else if (_selectedValue == 'daily') {
|
||||
logger.i('HMMM');
|
||||
_latestDate = _latestDate.add(const Duration(days: 1));
|
||||
}
|
||||
if (!_latestDate.isAfter(DateTime.now())) {
|
||||
final items = ref.watch(itemsProvider);
|
||||
|
||||
switch (items) {
|
||||
case AsyncError(:final error):
|
||||
logger.i('Error: $error');
|
||||
case AsyncData(:final value):
|
||||
final allItems = value;
|
||||
|
||||
_buildList(allItems);
|
||||
default:
|
||||
logger.i('Hmmm, how can we help?');
|
||||
} // get current date
|
||||
}
|
||||
|
||||
// show past 7 days starting 14 days ago
|
||||
}
|
||||
|
||||
String _getText() {
|
||||
final dateFormat = DateFormat('dd. MMM yyyy');
|
||||
|
||||
if (_selectedValue == 'weekly' &&
|
||||
_latestDate.isAfter(DateTime.now().subtract(const Duration(days: 6)))) {
|
||||
return ' this past week';
|
||||
} else if (_selectedValue == 'weekly' &&
|
||||
!_latestDate
|
||||
.isAfter(DateTime.now().subtract(const Duration(days: 6)))) {
|
||||
return ' '
|
||||
'from'
|
||||
' ${dateFormat.format(_latestDate.subtract(const Duration(days: 6)))}'
|
||||
' to ${dateFormat.format(_latestDate)}';
|
||||
}
|
||||
final formattedDate = dateFormat.format(_latestDate);
|
||||
// return ' on ${_latestDate.toString().substring(6, 10)}';
|
||||
return ' on $formattedDate';
|
||||
}
|
||||
|
||||
List<dynamic> thisWeekItems = [];
|
||||
List<dynamic> todayItems = [];
|
||||
String? _selectedValue = 'weekly';
|
||||
|
||||
double _thisWeekCompleted = 0;
|
||||
double _thisWeekPlanned = 0;
|
||||
double _maxAmount = 0;
|
||||
DateTime _latestDate = DateTime.now();
|
||||
double _todayCompleted = 0;
|
||||
double _todayPlanned = 0;
|
||||
|
||||
List<DataPoint> _weeklyPlannedEntries = <DataPoint>[];
|
||||
List<DataPoint> _weeklyWorkedEntries = <DataPoint>[];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final items = ref.watch(itemsProvider);
|
||||
|
||||
switch (items) {
|
||||
case AsyncError(:final error):
|
||||
logger.i('Error: $error');
|
||||
case AsyncData(:final value):
|
||||
final allItems = value;
|
||||
_buildList(allItems);
|
||||
default:
|
||||
logger.i('Hmmm, how can we help?');
|
||||
}
|
||||
|
||||
final firstDate = _weeklyPlannedEntries.elementAtOrNull(0)!.date;
|
||||
logger.i('HMMM $firstDate');
|
||||
final flSpots = <FlSpot>[];
|
||||
for (final dataPoint in _weeklyPlannedEntries) {
|
||||
final xValue = _daysBetween(_weeklyPlannedEntries[0].date, dataPoint.date)
|
||||
.toDouble();
|
||||
|
||||
final lcbd = FlSpot(
|
||||
xValue,
|
||||
dataPoint.progress,
|
||||
);
|
||||
|
||||
flSpots.add(lcbd);
|
||||
}
|
||||
|
||||
final weeklyWorkedSpots = <FlSpot>[];
|
||||
for (final dataPoint in _weeklyWorkedEntries) {
|
||||
final xValue =
|
||||
_daysBetween(_weeklyWorkedEntries[0].date, dataPoint.date).toDouble();
|
||||
final lcbd = FlSpot(xValue, dataPoint.progress);
|
||||
|
||||
weeklyWorkedSpots.add(lcbd);
|
||||
}
|
||||
final gradientColors = [
|
||||
Colors.cyan,
|
||||
Colors.blueAccent,
|
||||
];
|
||||
final gradient2Colors = [
|
||||
Colors.amber,
|
||||
Colors.amberAccent,
|
||||
];
|
||||
final df = DateFormat('dd. MMMM');
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 75,
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
margin: const EdgeInsets.only(bottom: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: _showPreviousWeek,
|
||||
style: ButtonStyle(
|
||||
shape: WidgetStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Text('Previous'),
|
||||
),
|
||||
DropdownButton<String>(
|
||||
value: _selectedValue,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_latestDate = DateTime.now();
|
||||
_selectedValue = value;
|
||||
});
|
||||
},
|
||||
items: const [
|
||||
DropdownMenuItem(
|
||||
value: 'daily',
|
||||
child: Text('Daily'),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: 'weekly',
|
||||
child: Text('Weekly'),
|
||||
),
|
||||
],
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _showNextWeek,
|
||||
style: ButtonStyle(
|
||||
shape: WidgetStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Text('Next'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_selectedValue == 'weekly') ...[
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.only(bottom: 15, top: 15, right: 15, left: 5),
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.90,
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
minY: 0,
|
||||
maxY: _maxAmount,
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
isCurved: true,
|
||||
preventCurveOverShooting: true,
|
||||
spots: flSpots,
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
gradient: LinearGradient(
|
||||
colors: gradientColors
|
||||
.map((color) => color.withOpacity(0.6))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
LineChartBarData(
|
||||
color: Colors.amber,
|
||||
isCurved: true,
|
||||
isStrokeCapRound: true,
|
||||
preventCurveOverShooting: true,
|
||||
spots: weeklyWorkedSpots,
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
gradient: LinearGradient(
|
||||
colors: gradient2Colors
|
||||
.map((color) => color.withOpacity(0.6))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
titlesData: FlTitlesData(
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
reservedSize: 30,
|
||||
interval: 20,
|
||||
),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
interval: 20,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 30,
|
||||
getTitlesWidget: (value, meta) => Text(
|
||||
'$value l',
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
fontStyle: FontStyle.italic,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
// interval: 0.25,
|
||||
),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
getTitlesWidget: (value, meta) => Text(
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
fontStyle: FontStyle.italic,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
df.format(
|
||||
DateTime(
|
||||
firstDate.year,
|
||||
firstDate.month,
|
||||
firstDate.day,
|
||||
).add(Duration(days: value.round())),
|
||||
),
|
||||
),
|
||||
showTitles: true,
|
||||
interval: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (_selectedValue == 'daily') ...[
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.90,
|
||||
height: MediaQuery.of(context).size.height * 0.5,
|
||||
child: PieChart(
|
||||
PieChartData(
|
||||
sectionsSpace: 0,
|
||||
sections: [
|
||||
PieChartSectionData(
|
||||
value: _todayCompleted, // Progress
|
||||
color: Theme.of(context).colorScheme.primaryFixedDim,
|
||||
radius: 60,
|
||||
title: (_todayPlanned > 0)
|
||||
// ignore: lines_longer_than_80_chars
|
||||
? '${((_todayCompleted / _todayPlanned) * 100).floorToDouble()} %'
|
||||
: '0 %',
|
||||
|
||||
titleStyle: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
PieChartSectionData(
|
||||
titleStyle: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primaryFixedDim,
|
||||
),
|
||||
value: (_todayPlanned > 0 && _todayCompleted > 0)
|
||||
? (_todayPlanned - _todayCompleted)
|
||||
: 1, // Total - progress
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
radius: 60,
|
||||
showTitle: _todayPlanned == 0 ||
|
||||
(_todayPlanned > 0 && _todayCompleted == 0),
|
||||
title: '0 %',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.90,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
// ignore: lines_longer_than_80_chars
|
||||
'${(_selectedValue == 'weekly') ? _thisWeekCompleted : _todayCompleted} liters drunk${_getText()}',
|
||||
),
|
||||
Text(
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
// ignore: lines_longer_than_80_chars
|
||||
'Out of a total goal of ${(_selectedValue == 'weekly') ? _thisWeekPlanned : _todayPlanned} liters',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
int _daysBetween(DateTime from, DateTime to) {
|
||||
final d1 = DateTime(from.year, from.month, from.day);
|
||||
final d2 = DateTime(to.year, to.month, to.day);
|
||||
return (d2.difference(d1).inHours / 24).round();
|
||||
}
|
||||
}
|
123
app/lib/pages/reset_subpage.dart
Normal file
|
@ -0,0 +1,123 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart';
|
||||
import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart';
|
||||
import 'package:habitrack_app/sembast/hydration.dart';
|
||||
import 'package:habitrack_app/sembast/tasks_list.dart';
|
||||
import 'package:habitrack_app/sembast/timer.dart';
|
||||
|
||||
class ResetSubpageButton extends ConsumerStatefulWidget {
|
||||
const ResetSubpageButton({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ResetSubpageButton> createState() => _ResetSubpageButtonState();
|
||||
}
|
||||
|
||||
class _ResetSubpageButtonState extends ConsumerState<ResetSubpageButton> {
|
||||
Future<void> _confirmPopup() async {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
title: Text(
|
||||
'Are you sure?',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 25,
|
||||
),
|
||||
),
|
||||
scrollable: true,
|
||||
actions: <Widget>[
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
final controller = ref.watch(homeControllerProvider);
|
||||
final items = ref.watch(itemsProvider);
|
||||
switch (items) {
|
||||
case AsyncData(:final value):
|
||||
final items = value;
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
final item = items.elementAt(i);
|
||||
if (item is Hydration) {
|
||||
controller.delete(item.id);
|
||||
} else if (item is TasksItem) {
|
||||
controller.delete(item.id);
|
||||
} else if (item is TimerItem) {
|
||||
controller.delete(item.id);
|
||||
}
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
//DateTime due = DateTime.parse(formattedDate);
|
||||
// ignore: cascade_invocations
|
||||
},
|
||||
child: Text(
|
||||
'Yes',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 300,
|
||||
margin: const EdgeInsets.only(top: 7.5, bottom: 7.5),
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
minimumSize: const Size(10, 70),
|
||||
),
|
||||
onPressed: _confirmPopup,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.delete,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
Text(
|
||||
'Clear Database',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|