boyrobot 9 months ago
commit
68d3b84791
100 changed files with 6433 additions and 0 deletions
  1. 44 0
      .gitignore
  2. 45 0
      .metadata
  3. 16 0
      README.md
  4. 29 0
      analysis_options.yaml
  5. 13 0
      android/.gitignore
  6. 59 0
      android/app/build.gradle
  7. 8 0
      android/app/src/debug/AndroidManifest.xml
  8. 48 0
      android/app/src/main/AndroidManifest.xml
  9. 6 0
      android/app/src/main/java/io/github/jianboy/flutter/flutter_habit/MainActivity.java
  10. 12 0
      android/app/src/main/res/drawable-v21/launch_background.xml
  11. 12 0
      android/app/src/main/res/drawable/launch_background.xml
  12. BIN
      android/app/src/main/res/mipmap-hdpi/ic_launcher.png
  13. BIN
      android/app/src/main/res/mipmap-mdpi/ic_launcher.png
  14. BIN
      android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
  15. BIN
      android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  16. BIN
      android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  17. 18 0
      android/app/src/main/res/values-night/styles.xml
  18. 18 0
      android/app/src/main/res/values/styles.xml
  19. 8 0
      android/app/src/profile/AndroidManifest.xml
  20. 31 0
      android/build.gradle
  21. 3 0
      android/gradle.properties
  22. 5 0
      android/gradle/wrapper/gradle-wrapper.properties
  23. 11 0
      android/settings.gradle
  24. 34 0
      ios/.gitignore
  25. 26 0
      ios/Flutter/AppFrameworkInfo.plist
  26. 1 0
      ios/Flutter/Debug.xcconfig
  27. 1 0
      ios/Flutter/Release.xcconfig
  28. 483 0
      ios/Runner.xcodeproj/project.pbxproj
  29. 7 0
      ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  30. 8 0
      ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  31. 8 0
      ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  32. 87 0
      ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  33. 7 0
      ios/Runner.xcworkspace/contents.xcworkspacedata
  34. 8 0
      ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  35. 8 0
      ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  36. 13 0
      ios/Runner/AppDelegate.swift
  37. 122 0
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
  38. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
  39. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
  40. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
  41. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
  42. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
  43. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
  44. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
  45. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
  46. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
  47. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
  48. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
  49. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
  50. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
  51. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
  52. BIN
      ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
  53. 23 0
      ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
  54. BIN
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
  55. BIN
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
  56. BIN
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
  57. 5 0
      ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
  58. 37 0
      ios/Runner/Base.lproj/LaunchScreen.storyboard
  59. 26 0
      ios/Runner/Base.lproj/Main.storyboard
  60. 51 0
      ios/Runner/Info.plist
  61. 1 0
      ios/Runner/Runner-Bridging-Header.h
  62. 202 0
      lib/common/BaseArchitectural.dart
  63. 535 0
      lib/common/I18N.dart
  64. 16 0
      lib/common/LocalData.dart
  65. 48 0
      lib/common/SqfliteDataBase.dart
  66. 13 0
      lib/common/components/CoinAddContext.dart
  67. 224 0
      lib/common/components/PopMenus.dart
  68. 84 0
      lib/common/components/SliderConfirm.dart
  69. 145 0
      lib/common/components/UserInfoPopMenuContext.dart
  70. 84 0
      lib/common/components/UserListTile.dart
  71. 95 0
      lib/common/provider/ConfigProvider.dart
  72. 505 0
      lib/common/provider/DataProvider.dart
  73. 200 0
      lib/common/provider/NotificationProvider.dart
  74. 58 0
      lib/common/provider/ThemeProvider.dart
  75. 82 0
      lib/common/provider/UserProvider.dart
  76. 54 0
      lib/common/utils/ConvertUtils.dart
  77. 43 0
      lib/common/utils/VerificationUtils.dart
  78. 116 0
      lib/database/entity/BasicInfo.dart
  79. 83 0
      lib/database/entity/ExerciseInfo.dart
  80. 83 0
      lib/database/entity/FoodInfo.dart
  81. 226 0
      lib/database/entity/LifeInfo.dart
  82. 72 0
      lib/database/entity/ScheduledExercise.dart
  83. 83 0
      lib/database/entity/SportInfo.dart
  84. 149 0
      lib/database/entity/StudyInfo.dart
  85. 11 0
      lib/database/mapper/BasicInfoMapper.dart
  86. 11 0
      lib/database/mapper/ExerciseInfoMapper.dart
  87. 11 0
      lib/database/mapper/FoodInfoMapper.dart
  88. 11 0
      lib/database/mapper/LifeInfoMapper.dart
  89. 11 0
      lib/database/mapper/ScheduledExerciseMapper.dart
  90. 12 0
      lib/database/mapper/SportInfoMapper.dart
  91. 11 0
      lib/database/mapper/StudyInfoMapper.dart
  92. 49 0
      lib/main.dart
  93. 20 0
      lib/network/Api.dart
  94. 633 0
      lib/network/Repository.dart
  95. 21 0
      lib/network/Status.dart
  96. 167 0
      lib/view/HomePage.dart
  97. 107 0
      lib/view/LoadingPage.dart
  98. 195 0
      lib/view/context/BasicInfoContext.dart
  99. 278 0
      lib/view/context/ExerciseInfoContext.dart
  100. 343 0
      lib/view/context/LifeInfoContext.dart

+ 44 - 0
.gitignore

@@ -0,0 +1,44 @@
+# 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
+.packages
+.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 - 0
.metadata

@@ -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.
+
+version:
+  revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+  channel: unknown
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+  platforms:
+    - platform: root
+      create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+      base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+    - platform: android
+      create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+      base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+    - platform: ios
+      create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+      base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+    - platform: linux
+      create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+      base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+    - platform: macos
+      create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+      base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+    - platform: web
+      create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+      base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+    - platform: windows
+      create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+      base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
+
+  # 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'

+ 16 - 0
README.md

@@ -0,0 +1,16 @@
+# flutter_habit
+
+A new Flutter project.
+
+## Getting Started
+
+This project is a starting point for a Flutter application.
+
+A few resources to get you started if this is your first Flutter project:
+
+- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
+- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
+
+For help getting started with Flutter development, view the
+[online documentation](https://docs.flutter.dev/), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.

+ 29 - 0
analysis_options.yaml

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

+ 13 - 0
android/.gitignore

@@ -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

+ 59 - 0
android/app/build.gradle

@@ -0,0 +1,59 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion 33
+    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 "io.github.jianboy.flutter.flutter_habit"
+        // 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.
+        minSdkVersion flutter.minSdkVersion
+        targetSdkVersion 33
+        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 '../..'
+}

+ 8 - 0
android/app/src/debug/AndroidManifest.xml

@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.github.jianboy.flutter.flutter_habit">
+    <!-- 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>

+ 48 - 0
android/app/src/main/AndroidManifest.xml

@@ -0,0 +1,48 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.github.jianboy.flutter.flutter_habit">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
+   <application
+        android:label="flutter_habit"
+        android:name="${applicationName}"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            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>
+        <receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
+        <receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED"></action>
+            </intent-filter>
+        </receiver>
+        <!-- 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>
+</manifest>

+ 6 - 0
android/app/src/main/java/io/github/jianboy/flutter/flutter_habit/MainActivity.java

@@ -0,0 +1,6 @@
+package io.github.jianboy.flutter.flutter_habit;
+
+import io.flutter.embedding.android.FlutterActivity;
+
+public class MainActivity extends FlutterActivity {
+}

+ 12 - 0
android/app/src/main/res/drawable-v21/launch_background.xml

@@ -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 - 0
android/app/src/main/res/drawable/launch_background.xml

@@ -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
android/app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


+ 18 - 0
android/app/src/main/res/values-night/styles.xml

@@ -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 - 0
android/app/src/main/res/values/styles.xml

@@ -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>

+ 8 - 0
android/app/src/profile/AndroidManifest.xml

@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.github.jianboy.flutter.flutter_habit">
+    <!-- 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>

+ 31 - 0
android/build.gradle

@@ -0,0 +1,31 @@
+buildscript {
+    ext.kotlin_version = '1.7.10'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:7.2.2'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+    project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 3 - 0
android/gradle.properties

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

+ 5 - 0
android/gradle/wrapper/gradle-wrapper.properties

@@ -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.3.3-bin.zip

+ 11 - 0
android/settings.gradle

@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

+ 34 - 0
ios/.gitignore

@@ -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 - 0
ios/Flutter/AppFrameworkInfo.plist

@@ -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>11.0</string>
+</dict>
+</plist>

+ 1 - 0
ios/Flutter/Debug.xcconfig

@@ -0,0 +1 @@
+#include "Generated.xcconfig"

+ 1 - 0
ios/Flutter/Release.xcconfig

@@ -0,0 +1 @@
+#include "Generated.xcconfig"

+ 483 - 0
ios/Runner.xcodeproj/project.pbxproj

@@ -0,0 +1,483 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 54;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+		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 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>"; };
+		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 */
+		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 */,
+			);
+			sourceTree = "<group>";
+		};
+		97C146EF1CF9000F007C117D /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				97C146EE1CF9000F007C117D /* Runner.app */,
+			);
+			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 */
+		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 = {
+				LastUpgradeCheck = 1300;
+				ORGANIZATIONNAME = "";
+				TargetAttributes = {
+					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 */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		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 = (
+			);
+			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 */
+		97C146EA1CF9000F007C117D /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase 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;
+				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;
+				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 = 11.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 = io.github.jianboy.flutter.flutterHabit;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Profile;
+		};
+		97C147031CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				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;
+				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 = 11.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;
+				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;
+				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 = 11.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 = io.github.jianboy.flutter.flutterHabit;
+				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 = io.github.jianboy.flutter.flutterHabit;
+				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 */
+		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 - 0
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:">
+   </FileRef>
+</Workspace>

+ 8 - 0
ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.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>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>

+ 8 - 0
ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings

@@ -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>

+ 87 - 0
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1300"
+   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>
+      </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 - 0
ios/Runner.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>

+ 8 - 0
ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.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>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>

+ 8 - 0
ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings

@@ -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 - 0
ios/Runner/AppDelegate.swift

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

+ 122 - 0
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json

@@ -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"
+  }
+}

BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png


BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png


+ 23 - 0
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json

@@ -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
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png


BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png


BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png


+ 5 - 0
ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md

@@ -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 - 0
ios/Runner/Base.lproj/LaunchScreen.storyboard

@@ -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 - 0
ios/Runner/Base.lproj/Main.storyboard

@@ -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>

+ 51 - 0
ios/Runner/Info.plist

@@ -0,0 +1,51 @@
+<?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>Flutter Habit</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>flutter_habit</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>UIViewControllerBasedStatusBarAppearance</key>
+	<false/>
+	<key>CADisableMinimumFrameDurationOnPhone</key>
+	<true/>
+	<key>UIApplicationSupportsIndirectInputEvents</key>
+	<true/>
+</dict>
+</plist>

+ 1 - 0
ios/Runner/Runner-Bridging-Header.h

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

+ 202 - 0
lib/common/BaseArchitectural.dart

@@ -0,0 +1,202 @@
+
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+
+import 'SqfliteDataBase.dart';
+
+abstract class BaseService extends ChangeNotifier {
+  BaseService(BuildContext context) {
+    init(context);
+  }
+
+  void init(BuildContext context) {}
+}
+
+abstract class BaseModel extends ChangeNotifier {
+  BaseModel(BuildContext context) {
+    init(context);
+    asyncInit(context);
+  }
+
+  void init(BuildContext context) {}
+
+  Future<void> asyncInit(BuildContext context) async {}
+
+  void refresh() {
+    notifyListeners();
+  }
+}
+/* demo mapper
+class {ENTITY_NAME}Mapper extends CommonMapper<{ENTITY_NAME}> {
+  {ENTITY_NAME}Mapper._() : super({ENTITY_NAME}());
+  static {ENTITY_NAME}Mapper _instance = {ENTITY_NAME}Mapper._();
+
+  factory {ENTITY_NAME}Mapper() {
+    return _instance;
+  }
+}
+ */
+abstract class CommonMapper<T extends dynamic> {
+  dynamic _currentTypeInstance;
+
+
+  CommonMapper(dynamic entityI) {
+    _currentTypeInstance = entityI;
+  }
+
+//  Batch batch;
+//
+//  void startBatch() {
+//    if (batch == null) {
+//      batch = SqfliteDataBase.getInstance().batch();
+//    }
+//    else {
+//      throw "already started a batch";
+//    }
+//  }
+//
+//  Future<void> commitBatch() async {
+//    if (batch == null) {
+//      throw "there is no batch exist";
+//    }
+//    else {
+//      List list = await batch.commit();
+//      batch = null;
+//      debugPrint(list.toString());
+//    }
+//  }
+
+  // C
+  Future<bool> insert(T entity) async {
+    try {
+      Database database = SqfliteDataBase.getInstance();
+      await database.insert(_currentTypeInstance.tableName, entity.value);
+      debugPrint("[CommonMapper] insert: ${entity.toString()}");
+      return true;
+    } catch (e) {
+      debugPrint("[CommonMapper] insert fail ${e.toString()}");
+      return false;
+    }
+  }
+
+  // R
+  Future<List<T>> selectAll({int limit, String orderBy}) async {
+    try {
+      Database database = SqfliteDataBase.getInstance();
+      List<Map<String, dynamic>> dbResult = await database.query(
+        _currentTypeInstance.tableName,
+        limit: limit,
+        orderBy: orderBy,
+      );
+      debugPrint(
+          "[CommonMapper] selectAll result: ${dbResult.length.toString()}");
+      return _currentTypeInstance.resultAsList(dbResult);
+    } catch (e) {
+      debugPrint("[CommonMapper] selectAll fail ${e.toString()}");
+      return null;
+    }
+  }
+
+  Future<List<T>> selectWhere(String where,
+      {int limit, String orderBy}) async {
+    try {
+      Database database = SqfliteDataBase.getInstance();
+      List<Map<String, dynamic>> dbResult = await database.query(
+        _currentTypeInstance.tableName,
+        limit: limit,
+        orderBy: orderBy,
+        where: where,
+      );
+      debugPrint("[CommonMapper] selectWhere result: ${dbResult.length.toString()}");
+      return _currentTypeInstance.resultAsList(dbResult);
+    } catch (e) {
+      debugPrint("[CommonMapper] selectWhere fail ${e.toString()}");
+      return null;
+    }
+  }
+
+  Future<List<T>> selectMatchBy(dynamic entity,
+      {int limit, String orderBy}) async {
+    try {
+      Database database = SqfliteDataBase.getInstance();
+
+      String matches = "";
+      entity.value.forEach((key, value) {
+        if (value != null) {
+          matches += "$key = '${value.toString()}' AND ";
+        }
+      });
+      if (matches.endsWith("AND ")) {
+        matches = matches.substring(0, matches.length - 4);
+      }
+
+      List<Map<String, dynamic>> dbResult = await database.query(
+        _currentTypeInstance.tableName,
+        where: matches,
+        limit: limit,
+        orderBy: orderBy,
+      );
+
+      debugPrint(
+          "[CommonMapper] selectMatchBy result: ${dbResult.length.toString()}");
+      return _currentTypeInstance.resultAsList(dbResult);
+    } catch (e) {
+      debugPrint("[CommonMapper] selectMatchBy fail ${e.toString()}");
+      return null;
+    }
+  }
+
+  // U
+//  Future<bool> updateByFirstKey(T entity) async {
+//    try {
+//      Database database = SqfliteDataBase.getInstance();
+//      await database.update(currentTypeInstance.tableName, entity.value,
+//          where:
+//              "${entity.value.keys.elementAt(0)} = '${entity.value.values.elementAt(0)}'");
+//      debugPrint("[CommonMapper] update: ${entity.toString()}");
+//      return true;
+//    } catch (e) {
+//      debugPrint("[CommonMapper] update fail ${e.toString()}");
+//      return false;
+//    }
+//  }
+
+  Future<bool> updateByFirstKeySelective(T entity) async {
+    try {
+      Database database = SqfliteDataBase.getInstance();
+
+      entity.value.removeWhere((_, value) {
+        return value == null;
+      });
+
+      await database.update(_currentTypeInstance.tableName, entity.value,
+          where:
+              "${entity.value.keys.elementAt(0)} = '${entity.value.values.elementAt(0)}'");
+      debugPrint("[CommonMapper] update where: ${entity.toString()}");
+      return true;
+    } catch (e) {
+      debugPrint("[CommonMapper] update fail ${e.toString()}");
+      return false;
+    }
+  }
+
+  // D
+  Future<int> delete(T entity) async {
+    Database database = SqfliteDataBase.getInstance();
+
+    String matches = "";
+    entity.value.forEach((key, value) {
+      if (value != null) {
+        matches += "$key = '${value.toString()}' AND ";
+      }
+    });
+    if (matches.endsWith("AND ")) {
+      matches = matches.substring(0, matches.length - 4);
+    }
+
+    int count =
+        await database.delete(_currentTypeInstance.tableName, where: matches);
+    debugPrint("[CommonMapper] delete $count row");
+    return count;
+  }
+}

+ 535 - 0
lib/common/I18N.dart

@@ -0,0 +1,535 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_habit/common/LocalData.dart';
+
+class I18N {
+  static String _language;
+
+  static void init() {
+    _language = LocalData.getInstance().getString("language");
+    if (_language ==null) {
+      _language = "cn";
+      LocalData.getInstance().setString("language", _language);
+    }
+    debugPrint("init I18N to $_language");
+  }
+
+  static String of(String key) {
+    return wordMap[_language][key] ?? "${_language.toUpperCase()}_$key";
+//    return key.toString();
+  }
+
+  static String getLanguage() {
+    return _language;
+  }
+
+  static void setLanguage(String language) {
+    _language = language;
+    LocalData.getInstance().setString("language", _language);
+  }
+}
+
+Map<String, Map<String, String>> wordMap = {
+  "cn" : {
+    "" : "",
+    "7日运动次数" : "7日运动次数",
+    "7日运动总消耗" : "7日运动总消耗",
+    "BMI" : "BMI",
+    "ID" : "ID",
+    "MarkDown预览" : "MarkDown预览",
+    "不能关注自己" : "不能关注自己",
+    "裁剪图片" : "裁剪图片",
+    "长按可删除食物" : "长按可删除食物",
+    "长按可删除运动" : "长按可删除运动",
+    "长度为2-10个不包括任何符号的字符" : "长度为2-10个不包括任何符号的字符",
+    "吃饭时间" : "吃饭时间",
+    "迟到次数" : "迟到次数",
+    "从相册中选择" : "从相册中选择",
+    "从云端下载数据" : "从云端下载数据",
+    "打卡成功" : "打卡成功",
+    "打卡时段" : "打卡时段",
+    "打卡提醒" : "打卡提醒",
+    "打卡完成度" : "打卡完成度",
+    "待完成计划数" : "待完成计划数",
+    "登出" : "登出",
+    "登录" : "登录",
+    "登录信息过期" : "登录信息过期",
+    "登陆成功" : "登陆成功",
+    "调整数据" : "调整数据",
+    "非打卡时间打卡成功" : "非打卡时间打卡成功",
+    "否" : "否",
+    "该食物已存在" : "该食物已存在",
+    "该邮箱未注册" : "该邮箱未注册",
+    "该邮箱已存在" : "该邮箱已存在",
+    "该运动已存在" : "该运动已存在",
+    "概览" : "概览",
+    "感谢您的支持,优惠口令已发送至您的邮箱" : "感谢您的支持,优惠口令已发送至您的邮箱",
+    "关注" : "关注",
+    "关注成功" : "关注成功",
+    "官方合作" : "官方合作",
+    "花费" : "花费",
+    "花费记录" : "花费记录",
+    "滑动来打卡" : "滑动来打卡",
+    "滑动来覆盖今日数据" : "滑动来覆盖今日数据",
+    "滑动来删除该条数据" : "滑动来删除该条数据",
+    "滑动来完成计划" : "滑动来完成计划",
+    "欢迎" : "欢迎",
+    "获取验证码" : "获取验证码",
+    "基本信息" : "基本信息",
+    "基本信息记录" : "基本信息记录",
+    "计划任务" : "计划任务",
+    "计划运动时长" : "计划运动时长",
+    "记录" : "记录",
+    "记录成功" : "记录成功",
+    "记录课程学习" : "记录课程学习",
+    "继续该操作吗?":"继续该操作吗?",
+    "简单" : "简单",
+    "结束时间" : "结束时间",
+    "金币" : "金币",
+    "金币不足" : "金币不足",
+    "今日" : "今日",
+    "今日状态" : "今日状态",
+    "进食量" : "进食量",
+    "卡路里消耗" : "卡路里消耗",
+    "课程难度" : "课程难度",
+    "课程学习" : "课程学习",
+    "课程学习详情" : "课程学习详情",
+    "课程主题" : "课程主题",
+    "课程主题不能为空" : "课程主题不能为空",
+    "库存" : "库存",
+    "库存不足" : "库存不足",
+    "快速注册" : "快速注册",
+    "困难" : "困难",
+    "来到" : "来到",
+    "立即获取优惠口令" : "立即获取优惠口令",
+    "连接失败" : "连接失败",
+    "两次输入不一致" : "两次输入不一致",
+    "每日打卡完成度" : "每日打卡完成度",
+    "每日课程数" : "每日课程数",
+    "密码" : "密码",
+    "密码不能为空" : "密码不能为空",
+    "男" : "男",
+    "您不能删除吃过的食物" : "您不能删除吃过的食物",
+    "您不能删除记录过的运动" : "您不能删除记录过的运动",
+    "您的账号由于存在恶意刷金币行为已被系统限制金币获取" : "您的账号由于存在恶意刷金币行为已被系统限制金币获取",
+    "女" : "女",
+    "拍照" : "拍照",
+    "排名" : "排名",
+    "排行榜" : "排行榜",
+    "起床打卡" : "起床打卡",
+    "起床打卡开始了" : "起床打卡开始了",
+    "起床打卡时间" : "起床打卡时间",
+    "起床时间" : "起床时间",
+    "起始时间" : "起始时间",
+    "请输入6位验证码" : "请输入6位验证码",
+    "请输入花费" : "请输入花费",
+    "请输入计划运动时长" : "请输入计划运动时长",
+    "请输入进食量" : "请输入进食量",
+    "请输入您的密码" : "请输入您的密码",
+    "请输入您的新密码" : "请输入您的新密码",
+    "请输入您的邮箱" : "请输入您的邮箱",
+    "请输入您注册时的邮箱" : "请输入您注册时的邮箱",
+    "请输入食物名称" : "请输入食物名称",
+    "请输入食物热量" : "请输入食物热量",
+    "请输入消耗热量" : "请输入消耗热量",
+    "请输入新用户名" : "请输入新用户名",
+    "请输入运动名称" : "请输入运动名称",
+    "请输入运动时长" : "请输入运动时长",
+    "请先登录" : "请先登录",
+    "请重复输入您的密码" : "请重复输入您的密码",
+    "请重复输入您的新密码" : "请重复输入您的新密码",
+    "取消" : "取消",
+    "取消关注" : "取消关注",
+    "取消关注成功" : "取消关注成功",
+    "缺席次数" : "缺席次数",
+    "确定" : "确定",
+    "日常生活" : "日常生活",
+    "日常生活记录" : "日常生活记录",
+    "三围信息" : "三围信息",
+    "删除成功" : "删除成功",
+    "商城" : "商城",
+    "商品详情" : "商品详情",
+    "上传同步成功" : "上传同步成功",
+    "摄入卡路里" : "摄入卡路里",
+    "摄入卡路里总量" : "摄入卡路里总量",
+    "设置" : "设置",
+    "身高" : "身高",
+    "生日" : "生日",
+    "食物名称" : "食物名称",
+    "食物热量" : "食物热量",
+    "食用次数" : "食用次数",
+    "是" : "是",
+    "是否迟到" : "是否迟到",
+    "是否缺席" : "是否缺席",
+    "输入用户名查询" : "输入用户名查询",
+    "输入有误" : "输入有误",
+    "数据管理" : "数据管理",
+    "睡觉打卡开始了" : "睡觉打卡开始了",
+    "睡觉时间" : "睡觉时间",
+    "睡眠时长" : "睡眠时长",
+    "睡眠时间" : "睡眠时间",
+    "私信" : "私信",
+    "松开以继续" : "松开以继续",
+    "搜索" : "搜索",
+    "搜索用户" : "搜索用户",
+    "提交" : "提交",
+    "体育锻炼" : "体育锻炼",
+    "体育锻炼记录" : "体育锻炼记录",
+    "体重" : "体重",
+    "体重信息" : "体重信息",
+    "添加计划任务" : "添加计划任务",
+    "添加计划任务成功" : "添加计划任务成功",
+    "添加食物" : "添加食物",
+    "添加数据后会展示对应图表" : "添加数据后会展示对应图表",
+    "添加运动" : "添加运动",
+    "通知开关" : "通知开关",
+    "同步数据到云端" : "同步数据到云端",
+    "臀围" : "臀围",
+    "完成" : "完成",
+    "晚安打卡" : "晚安打卡",
+    "晚饭打卡" : "晚饭打卡",
+    "晚饭时间" : "晚饭时间",
+    "忘记密码?" : "忘记密码?",
+    "未登录" : "未登录",
+    "未解决的问题数" : "未解决的问题数",
+    "未入榜" : "未入榜",
+    "未完成的作业" : "未完成的作业",
+    "未完成的作业数" : "未完成的作业数",
+    "我的关注" : "我的关注",
+    "无数据" : "无数据",
+    "午饭打卡" : "午饭打卡",
+    "午饭打卡开始了" : "午饭打卡开始了",
+    "午饭打卡时间" : "午饭打卡时间",
+    "午饭时间" : "午饭时间",
+    "午休打卡" : "午休打卡",
+    "午休打卡开始了" : "午休打卡开始了",
+    "午休打卡时间" : "午休打卡时间",
+    "午休时间" : "午休时间",
+    "下载同步成功" : "下载同步成功",
+    "详情" : "详情",
+    "向右滑动" : "向右滑动",
+    "消费" : "消费",
+    "消耗" : "消耗",
+    "消耗热量" : "消耗热量",
+    "新密码" : "新密码",
+    "信息" : "信息",
+    "性别" : "性别",
+    "胸围" : "胸围",
+    "修改成功" : "修改成功",
+    "修改密码成功" : "修改密码成功",
+    "修改生日" : "修改生日",
+    "修改头像" : "修改头像",
+    "修改性别" : "修改性别",
+    "修改用户名" : "修改用户名",
+    "选择食物" : "选择食物",
+    "学习" : "学习",
+    "验证码" : "验证码",
+    "验证码错误或过期" : "验证码错误或过期",
+    "验证码发送成功,5分钟内有效" : "验证码发送成功,5分钟内有效",
+    "腰围" : "腰围",
+    "一般" : "一般",
+    "已解决" : "已解决",
+    "用户" : "用户",
+    "用户名" : "用户名",
+    "用户设置" : "用户设置",
+    "邮件发送失败" : "邮件发送失败",
+    "邮箱" : "邮箱",
+    "邮箱不能为空" : "邮箱不能为空",
+    "邮箱格式有误" : "邮箱格式有误",
+    "邮箱或密码错误" : "邮箱或密码错误",
+    "有未解决的问题" : "有未解决的问题",
+    "有未完成的作业" : "有未完成的作业",
+    "语言" : "语言",
+    "遇到的问题" : "遇到的问题",
+    "预计消耗" : "预计消耗",
+    "云端无数据" : "云端无数据",
+    "运动次数" : "运动次数",
+    "运动类型" : "运动类型",
+    "运动名称" : "运动名称",
+    "运动时长" : "运动时长",
+    "早饭打卡" : "早饭打卡",
+    "早饭打卡开始了" : "早饭打卡开始了",
+    "早饭打卡时间" : "早饭打卡时间",
+    "早饭时间" : "早饭时间",
+    "支持MarkDown" : "支持MarkDown",
+    "重复购买不会重复扣费" : "重复购买不会重复扣费",
+    "重复密码" : "重复密码",
+    "重设密码" : "重设密码",
+    "重置密码" : "重置密码",
+    "重置数据" : "重置数据",
+    "主题" : "主题",
+    "注册" : "注册",
+    "注册成功" : "注册成功",
+    "注意" : "注意",
+    "字母开头,必须包含大小写字母,可以包含字母、数字、特殊符号\n长度为8~16位" : "字母开头,必须包含大小写字母,可以包含字母、数字、特殊符号\n长度为8~16位",
+    "总结" : "总结",
+    "最近" : "最近",
+    "昨夜睡眠时长" : "昨夜睡眠时长",
+    "作业是否完成" : "作业是否完成",
+  },
+  "en" : {
+    "":"",
+    "7日运动次数" : "7 days exercise times",
+    "7日运动总消耗" : "7 days consumption",
+    "BMI" : "BMI",
+    "ID" : "ID",
+    "MarkDown预览" : "MarkDown preview",
+    "不能关注自己" : "You can't follow yourself",
+    "裁剪图片" : "Crop Image",
+    "长按可删除食物" : "Long press to remove food",
+    "长按可删除运动" : "Long press to delete movement",
+    "长度为2-10个不包括任何符号的字符" : "Length of 2-10 characters without any symbols",
+    "吃饭时间" : "Mealtime",
+    "迟到次数" : "Late times",
+    "从相册中选择" : "Select from the album",
+    "从云端下载数据" : "Download data from the cloud",
+    "打卡成功" : "Clock in success",
+    "打卡时段" : "Clock in time",
+    "打卡提醒" : "Clock in reminded",
+    "打卡完成度" : "Clock in progress",
+    "待完成计划数" : "Schedule to be completed",
+    "登出" : "Sign out",
+    "登录" : "Sign in",
+    "登录信息过期" : "token expired",
+    "登陆成功" : "Sign in success",
+    "调整数据" : "Adjust data",
+    "非打卡时间打卡成功" : "Non-clocked time clocked in",
+    "否" : "no",
+    "该食物已存在" : "The food already exists",
+    "该邮箱未注册" : "This email is not registered",
+    "该邮箱已存在" : "The email already exists",
+    "该运动已存在" : "The movement already exists",
+    "概览" : "Overview",
+    "感谢您的支持,优惠口令已发送至您的邮箱" : "Thank you for your support. The token has been sent to your email",
+    "关注" : "follow",
+    "关注成功" : "follow success",
+    "官方合作" : "Authoritative",
+    "花费" : "Spend",
+    "花费记录" : "Spend record",
+    "滑动来打卡" : "Slide to clock in",
+    "滑动来覆盖今日数据" : "Slide to over today's data",
+    "滑动来删除该条数据" : "Slide to delete the data",
+    "滑动来完成计划" : "Slide to complete the schedule",
+    "欢迎" : "Welcome",
+    "获取验证码" : "Get AuthCode",
+    "基本信息" : "Basic information",
+    "基本信息记录" : "Basic information record",
+    "计划任务" : "Scheduled tasks",
+    "计划运动时长" : "Scheduled exercise time",
+    "记录" : "Record",
+    "记录成功" : "Record success",
+    "记录课程学习" : "Record course learning",
+    "继续该操作吗?":"Continue?",
+    "简单" : "Simple",
+    "结束时间" : "End time",
+    "金币" : "Coins",
+    "金币不足" : "Not enough Coins",
+    "今日" : "Today",
+    "今日状态" : "Today's state",
+    "进食量" : "Food-intake",
+    "卡路里消耗" : "Calorie consumption",
+    "课程难度" : "Course difficulty",
+    "课程学习" : "Course learning",
+    "课程学习详情" : "Course details",
+    "课程主题" : "Course topic",
+    "课程主题不能为空" : "Course topic can not be empty",
+    "库存" : "Stock",
+    "库存不足" : "Understock",
+    "快速注册" : "Fast sign up",
+    "困难" : "difficulty",
+    "来到" : "to",
+    "立即获取优惠口令" : "Get the discount token now",
+    "连接失败" : "Connection fail",
+    "两次输入不一致" : "The two inputs are inconsistent",
+    "每日打卡完成度" : "Daily clock in progress",
+    "每日课程数" : "Daily course count",
+    "密码" : "Password",
+    "密码不能为空" : "Password cannot be empty",
+    "男" : "Male",
+    "您不能删除吃过的食物" : "You cannot delete recorded food",
+    "您不能删除记录过的运动" : "You cannot delete recorded movements",
+    "您的账号由于存在恶意刷金币行为已被系统限制金币获取" : "Your account has been ban due to there is a malicious behavior of gain COINS",
+    "女" : "Female",
+    "拍照" : "Take a picture",
+    "排名" : "Ranking",
+    "排行榜" : "Ranking list",
+    "起床打卡" : "Wake-up clock in",
+    "起床打卡开始了" : "It's Wake-up clock in time",
+    "起床打卡时间" : "Wake-up clock in time",
+    "起床时间" : "Wake-up time",
+    "起始时间" : "Starting time",
+    "请输入6位验证码" : "Please enter a six-bit verification code",
+    "请输入花费" : "Please enter the cost",
+    "请输入计划运动时长" : "Please enter the duration of planned exercise",
+    "请输入进食量" : "Please enter food intake",
+    "请输入您的密码" : "Enter your password",
+    "请输入您的新密码" : "Enter your new password",
+    "请输入您的邮箱" : "Enter your email",
+    "请输入您注册时的邮箱" : "Enter your email that you sign up",
+    "请输入食物名称" : "Enter the name of the food",
+    "请输入食物热量" : "Enter the calorie of food",
+    "请输入消耗热量" : "Enter calories consumed",
+    "请输入新用户名" : "Please enter a new user name",
+    "请输入运动名称" : "Please enter the name of the movement",
+    "请输入运动时长" : "Please enter the duration of exercise",
+    "请先登录" : "Please log in first",
+    "请重复输入您的密码" : "Please repeat your password",
+    "请重复输入您的新密码" : "Please repeat your new password",
+    "取消" : "Cancel",
+    "取消关注" : "Unfollow",
+    "取消关注成功" : "Unfollow success",
+    "缺席次数" : "Absent times",
+    "确定" : "ok",
+    "日常生活" : "Daily life",
+    "日常生活记录" : "Daily life record",
+    "三围信息" : "BWH information",
+    "删除成功" : "deleted success",
+    "商城" : "Shopping mall",
+    "商品详情" : "Product details",
+    "上传同步成功" : "Upload synchronization successful",
+    "摄入卡路里" : "Calorie intake",
+    "摄入卡路里总量" : "Total calories intake",
+    "设置" : "Setting",
+    "身高" : "Stature",
+    "生日" : "Birthday",
+    "食物名称" : "Food name",
+    "食物热量" : "food energy",
+    "食用次数" : "Eat times",
+    "是" : "ok",
+    "是否迟到" : "Is late",
+    "是否缺席" : "Is absent",
+    "输入用户名查询" : "Enter the username to query",
+    "输入有误" : "Input error",
+    "数据管理" : "Data management",
+    "睡觉打卡开始了" : "It's sleep clock in time",
+    "睡觉时间" : "Sleep time",
+    "睡眠时长" : "Sleep duration",
+    "睡眠时间" : "Sleep time",
+    "私信" : "Private letter",
+    "松开以继续" : "Release to continue",
+    "搜索" : "Search",
+    "搜索用户" : "Search User",
+    "提交" : "Commit",
+    "体育锻炼" : "Physical exercise",
+    "体育锻炼记录" : "Physical exercise record",
+    "体重" : "Weight",
+    "体重信息" : "Weight information",
+    "添加计划任务" : "Add scheduled tasks",
+    "添加计划任务成功" : "Added scheduled task success",
+    "添加食物" : "Add food",
+    "添加数据后会展示对应图表" : "The corresponding chart will be displayed after the data is added",
+    "添加运动" : "Add movement",
+    "通知开关" : "Notification toggle",
+    "同步数据到云端" : "Synchronize data to the cloud",
+    "臀围" : "Hipline",
+    "完成" : "Accomplish",
+    "晚安打卡" : "Sleep clock in",
+    "晚饭打卡" : "Dinner clock in",
+    "晚饭时间" : "Dinner time",
+    "忘记密码?" : "Forget the password?",
+    "未登录" : "Not sign in",
+    "未解决的问题数" : "Unsolved issues count",
+    "未入榜" : "Not in listed",
+    "未完成的作业" : "Unfinished homework",
+    "未完成的作业数" : "Unfinished homework count",
+    "我的关注" : "My follows",
+    "无数据" : "Empty Data",
+    "午饭打卡" : "Lunch clock in",
+    "午饭打卡开始了" : "It's lunch clock in time",
+    "午饭打卡时间" : "Lunch clock in time",
+    "午饭时间" : "Lunch time",
+    "午休打卡" : "Lunch break clock in",
+    "午休打卡开始了" : "It's lunch break clock in time",
+    "午休打卡时间" : "Lunch break clock in time",
+    "午休时间" : "Lunch break time",
+    "下载同步成功" : "Download synchronization successful",
+    "详情" : "Details",
+    "向右滑动" : "Slide right",
+    "消费" : "Consumption",
+    "消耗" : "Consume",
+    "消耗热量" : "Calorie consumption",
+    "新密码" : "New password",
+    "信息" : "Information",
+    "性别" : "Gender",
+    "胸围" : "Chestline",
+    "修改成功" : "Modify success",
+    "修改密码成功" : "Password changed success",
+    "修改生日" : "Modify birthday",
+    "修改头像" : "Modify head picture",
+    "修改性别" : "Modify gender",
+    "修改用户名" : "Modify username",
+    "选择食物" : "Choice food",
+    "学习" : "Study",
+    "验证码" : "Auth code",
+    "验证码错误或过期" : "Auth code wrong or expired",
+    "验证码发送成功,5分钟内有效" : "Auth code sent successfully, valid within 5 minutes",
+    "腰围" : "Waistline",
+    "一般" : "General",
+    "已解决" : "solved",
+    "用户" : "User",
+    "用户名" : "User name",
+    "用户设置" : "User settings",
+    "邮件发送失败" : "Email sending failed",
+    "邮箱" : "Email",
+    "邮箱不能为空" : "Email cannot be empty",
+    "邮箱格式有误" : "Incorrect email format",
+    "邮箱或密码错误" : "Email or password wrong",
+    "有未解决的问题" : "There are unfinished tasks",
+    "有未完成的作业" : "There are unfinished homework",
+    "语言" : "Language",
+    "遇到的问题" : "Problems",
+    "预计消耗" : "Expected consumption",
+    "云端无数据" : "Empty cloud data",
+    "运动次数" : "Movement times",
+    "运动类型" : "Movement type",
+    "运动名称" : "Movement name",
+    "运动时长" : "Exercise duration",
+    "早饭打卡" : "breakfast clock in",
+    "早饭打卡开始了" : "It's breakfast clock in time",
+    "早饭打卡时间" : "Breakfast clock in time",
+    "早饭时间" : "Breakfast time",
+    "支持MarkDown" : "Support MarkDown",
+    "重复购买不会重复扣费" : "Repeat purchase will not be repeated deduction coins",
+    "重复密码" : "Repeat password",
+    "重设密码" : "Reset password",
+    "重置密码" : "Reset password",
+    "重置数据" : "Reset data",
+    "主题" : "Theme",
+    "注册" : "Sign up",
+    "注册成功" : "Sign up success",
+    "注意" : "Attention",
+    "字母开头,必须包含大小写字母,可以包含字母、数字、特殊符号\n长度为8~16位" : "Letter beginning, must contain case letters, can contain letters, Numbers, special symbol \n length of 8 to 16 bits",
+    "总结" : "Conclusion",
+    "最近" : "Recently",
+    "昨夜睡眠时长" : "Last night sleep time",
+    "作业是否完成" : "Is homework completed",
+  }
+};
+
+
+
+// A
+// B
+// C
+// D
+// E
+// F
+// G
+// H
+// I
+// J
+// K
+// L
+// M
+// N
+// O
+// P
+// Q
+// R
+// S
+// T
+// U
+// V
+// W
+// X
+// Y
+// Z

+ 16 - 0
lib/common/LocalData.dart

@@ -0,0 +1,16 @@
+import 'package:flutter/material.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+class LocalData {
+  static SharedPreferences _sharedPreferences;
+  static init() async {
+    if (_sharedPreferences == null) {
+      _sharedPreferences = await SharedPreferences.getInstance();
+      debugPrint("init LocalData");
+    }
+  }
+
+  static SharedPreferences getInstance() {
+    return _sharedPreferences;
+  }
+}

+ 48 - 0
lib/common/SqfliteDataBase.dart

@@ -0,0 +1,48 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_habit/database/entity/BasicInfo.dart';
+import 'package:flutter_habit/database/entity/ExerciseInfo.dart';
+import 'package:flutter_habit/database/entity/FoodInfo.dart';
+import 'package:flutter_habit/database/entity/LifeInfo.dart';
+import 'package:flutter_habit/database/entity/ScheduledExercise.dart';
+import 'package:flutter_habit/database/entity/SportInfo.dart';
+import 'package:flutter_habit/database/entity/StudyInfo.dart';
+import 'package:sqflite/sqflite.dart';
+
+class SqfliteDataBase {
+
+  static final String dbPath = "repository.db.sql";
+  static Database _database;
+
+  static Future<void> init() async {
+    if (_database == null || !_database.isOpen) {
+      String databasesPath = "${await getDatabasesPath()}/$dbPath";
+      _database = await openDatabase(databasesPath, version: 1);
+      await _createTablesIfExists();
+      debugPrint("init SqfliteDataBase");
+    }
+  }
+
+  static Database getInstance() {
+    return _database;
+  }
+
+  static Future<void> _createTablesIfExists() async {
+    await BasicInfo.create();
+    await LifeInfo.create();
+    await FoodInfo.create();
+    await ExerciseInfo.create();
+    await SportInfo.create();
+    await StudyInfo.create();
+    await ScheduledExercise.create();
+  }
+
+  static Future<void> resetTables() async {
+    await BasicInfo.recreate();
+    await LifeInfo.recreate();
+    await FoodInfo.recreate();
+    await ExerciseInfo.recreate();
+    await SportInfo.recreate();
+    await StudyInfo.recreate();
+    await ScheduledExercise.recreate();
+  }
+}

+ 13 - 0
lib/common/components/CoinAddContext.dart

@@ -0,0 +1,13 @@
+import 'package:flutter/material.dart';
+
+class CoinAddContext extends StatefulWidget {
+  @override
+  _CoinAddContextState createState() => _CoinAddContextState();
+}
+
+class _CoinAddContextState extends State<CoinAddContext> {
+  @override
+  Widget build(BuildContext context) {
+    return Container();
+  }
+}

+ 224 - 0
lib/common/components/PopMenus.dart

@@ -0,0 +1,224 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_habit/common/I18N.dart';
+import 'package:flutter_habit/common/provider/UserProvider.dart';
+import 'package:provider/provider.dart';
+
+import 'SliderConfirm.dart';
+import 'UserInfoPopMenuContext.dart';
+
+class PopMenus {
+  static Future<void> confirm({
+    @required BuildContext context,
+    Widget content,
+    @required Function function,
+  }) async {
+    await baseAlertMenu<int>(
+      context: context,
+      content: content ?? Text(I18N.of("继续该操作吗?")),
+      actions: [
+        TextButton(
+          child: Text(I18N.of("取消")),
+          onPressed: () {
+            Navigator.of(context).pop(0);
+          },
+        ),
+        TextButton(
+          child: Text(I18N.of("确定")),
+          onPressed: () {
+            Navigator.of(context).pop(1);
+          },
+        ),
+      ],
+    ).then<int>((value) {
+      if (value == 1) {
+        function();
+      }
+      return value;
+    });
+  }
+
+  static Future<void> sliderConfirm({
+    @required BuildContext context,
+    Widget content,
+    @required Function function,
+  }) async {
+    await baseMenu<int>(
+      context: context,
+      children: <Widget>[
+        Padding(
+          padding: EdgeInsets.all(24),
+          child: DefaultTextStyle(
+            style: Theme.of(context).textTheme.titleLarge,
+            child: content ?? Text(I18N.of("继续该操作吗?")),
+          ),
+        ),
+        Padding(
+          padding: EdgeInsets.only(left: 15, right: 15, bottom: 15),
+          child: SliderConfirm(
+            function: () => Navigator.of(context).pop(1),
+          ),
+        ),
+      ],
+      contentPadding: EdgeInsets.all(0),
+    ).then<int>((value) {
+      if (value == 1) {
+        function();
+      }
+      return value;
+    });
+  }
+
+  static Future<void> attention({
+    @required BuildContext context,
+    Widget content,
+  }) async {
+    await baseAlertMenu(
+      context: context,
+      content: content,
+    );
+  }
+
+  static Future<T> baseAlertMenu<T>({
+    @required BuildContext context,
+    Widget title,
+    Widget content,
+    List<Widget> actions,
+  }) async {
+    return await showDialog(
+      context: context,
+      builder: (context) => AlertDialog(
+        title: title ?? Text(I18N.of("注意")),
+        content: content ?? Text(I18N.of("注意")),
+        actions: actions ??
+            <Widget>[
+              TextButton(
+                child: Text(I18N.of("确定")),
+                onPressed: () {
+                  Navigator.of(context).pop();
+                },
+              ),
+            ],
+      ),
+    );
+  }
+
+  static Future<T> baseMenu<T>({
+    @required BuildContext context,
+    Widget title,
+    List<Widget> children,
+    EdgeInsetsGeometry contentPadding,
+  }) async {
+    return await showDialog(
+      context: context,
+      builder: (context) => SimpleDialog(
+        title: title ?? Text(I18N.of("注意")),
+        children: children ?? [Text(I18N.of("注意"))],
+        contentPadding:
+            contentPadding ?? EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0),
+      ),
+    );
+  }
+
+  static Future<String> datePicker({
+    @required BuildContext context,
+  }) async {
+    return await showDatePicker(
+      context: context,
+      initialDate: DateTime.now().subtract(Duration(days: 3650)),
+      firstDate: DateTime.now().subtract(Duration(days: 36500)),
+      lastDate: DateTime.now(),
+      initialDatePickerMode: DatePickerMode.year,
+    ).then((onValue) {
+      if (onValue != null) {
+        return onValue.toIso8601String().toString();
+      }
+      return null;
+    });
+  }
+
+  static Future<String> userInfo({
+    @required BuildContext context,
+    @required int uid,
+  }) async {
+    return await baseMenu(
+      context: context,
+      title: Text(I18N.of("信息")),
+      children: <Widget>[
+        UserInfoPopMenuContext(uid),
+      ],
+      contentPadding: EdgeInsets.only(top: 10, left: 20, right: 20, bottom: 10),
+    );
+  }
+
+  static Future<void> ban(BuildContext context) async {
+    await baseMenu(
+      context: context,
+      contentPadding: EdgeInsets.all(16),
+      children: [
+        Row(
+          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          children: <Widget>[
+            Icon(
+              Icons.warning,
+              size: 80,
+              color: Theme.of(context).unselectedWidgetColor,
+            ),
+            Container(),
+            Container(
+              child: Text(
+                I18N.of("您的账号由于存在恶意刷金币行为已被系统限制金币获取"),
+                maxLines: 3,
+              ),
+              width: 200,
+            ),
+          ],
+        ),
+      ],
+    );
+  }
+
+  static Future<void> coinAdd({
+    @required BuildContext context,
+    @required int addedCoins,
+  }) async {
+    await baseMenu(
+      context: context,
+      title: Text(I18N.of("打卡成功")),
+      children: [
+        Row(
+          mainAxisAlignment: MainAxisAlignment.center,
+          crossAxisAlignment: CrossAxisAlignment.end,
+          children: <Widget>[
+            Icon(
+              Icons.monetization_on,
+              size: 80,
+              color: Theme.of(context).colorScheme.secondary,
+            ),
+            Text(
+              " + ",
+              style: TextStyle(
+                fontSize: 40,
+              ),
+            ),
+            Text(
+              " ${addedCoins.toString()} ",
+              style: TextStyle(
+                fontSize: 60,
+                color: Theme.of(context).colorScheme.secondary,
+              ),
+            ),
+          ],
+        ),
+        Container(height: 10,),
+        Center(
+          child: Text(
+            "${Provider.of<UserProvider>(context, listen: false).coins} -> ${Provider.of<UserProvider>(context, listen: false).coins + addedCoins}",
+            style: TextStyle(
+              color: Theme.of(context).unselectedWidgetColor,
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+}

+ 84 - 0
lib/common/components/SliderConfirm.dart

@@ -0,0 +1,84 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_habit/common/I18N.dart';
+
+class SliderConfirm extends StatefulWidget {
+  final Function function;
+
+  SliderConfirm({@required this.function});
+
+  @override
+  _SliderConfirmState createState() => _SliderConfirmState();
+}
+
+class _SliderConfirmState extends State<SliderConfirm> {
+  double currentValue;
+  bool isEnd;
+  bool isFormStart;
+
+  @override
+  void initState() {
+    
+    super.initState();
+    currentValue = 0;
+    isEnd = false;
+    isFormStart = false;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Stack(
+      children: <Widget>[
+        Center(
+          child: Container(
+            decoration: BoxDecoration(
+              color: Theme.of(context).focusColor,
+              borderRadius: BorderRadius.circular(100),
+            ),
+            child: Slider(
+              activeColor: Theme.of(context).colorScheme.secondary,
+              inactiveColor: Colors.transparent,
+              value: currentValue,
+              min: 0,
+              max: 1,
+              onChanged: (value) {
+                if (value <= 0.1) {
+                  currentValue = value;
+                  isFormStart = true;
+                  setState(() {});
+                }
+                if (isFormStart) {
+                  currentValue = value;
+                  isEnd = currentValue == 1;
+                  setState(() {});
+                }
+              },
+              onChangeEnd: (value) {
+                if (isEnd) {
+                  widget.function();
+                } else {
+                  isFormStart = false;
+                  currentValue = 0;
+                  setState(() {});
+                }
+              },
+            ),
+          ),
+        ),
+        Center(
+          child: isEnd
+              ? Text(
+                  I18N.of("松开以继续"),
+                  style: Theme.of(context)
+                      .textTheme
+                      .titleSmall
+                      .copyWith(color: Theme.of(context).colorScheme.secondary),
+                )
+              : Text(
+                  I18N.of("向右滑动"),
+                  style: Theme.of(context).textTheme.titleLarge,
+                ),
+        ),
+      ],
+    );
+  }
+}

+ 145 - 0
lib/common/components/UserInfoPopMenuContext.dart

@@ -0,0 +1,145 @@
+
+import 'package:flutter/material.dart';
+import 'package:flutter_habit/common/I18N.dart';
+import 'package:flutter_habit/common/components/PopMenus.dart';
+import 'package:flutter_habit/common/provider/UserProvider.dart';
+import 'package:flutter_habit/network/Repository.dart';
+import 'package:provider/provider.dart';
+
+class UserInfoPopMenuContext extends StatefulWidget {
+  final int uid;
+
+  UserInfoPopMenuContext(this.uid);
+
+  @override
+  _UserInfoPopMenuContextState createState() => _UserInfoPopMenuContextState();
+}
+
+class _UserInfoPopMenuContextState extends State<UserInfoPopMenuContext> {
+  List<int> photo;
+  Map userInfo;
+  int coins;
+
+  bool isFollowed;
+
+  @override
+  void initState() {
+    
+    super.initState();
+    photo = null;
+    userInfo = {};
+    coins = -1;
+    isFollowed = false;
+    getData();
+  }
+
+  Future<void> getData() async {
+    UserProvider userProvider =
+        Provider.of<UserProvider>(context, listen: false);
+    isFollowed = await Repository.getInstance().getFollowState(
+        context, userProvider.token, userProvider.uid, widget.uid);
+    setState(() {});
+    userInfo = await Repository.getInstance().getUserInfo(widget.uid);
+    setState(() {});
+    coins = await Repository.getInstance().getCoin(widget.uid);
+    setState(() {});
+    photo = await Repository.getInstance().getPhoto(widget.uid);
+    setState(() {});
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    double size = 80;
+    return Column(
+      children: <Widget>[
+        Divider(),
+        Row(
+          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          crossAxisAlignment: CrossAxisAlignment.center,
+          children: <Widget>[
+            photo == null || photo.isEmpty
+                ? Icon(
+                    Icons.account_circle,
+                    size: size,
+                    color: Theme.of(context).unselectedWidgetColor,
+                  )
+                : ClipOval(
+                    child: Image.memory(
+                      photo,
+                      width: size,
+                      height: size,
+                    ),
+                  ),
+            Column(
+              crossAxisAlignment: CrossAxisAlignment.start,
+              children: <Widget>[
+                Text("${I18N.of("用户名")}: ", style: TextStyle(height: 1.5)),
+                Text("${I18N.of("生日")}: ", style: TextStyle(height: 1.5)),
+                Text("${I18N.of("性别")}: ", style: TextStyle(height: 1.5)),
+                Text("${I18N.of("金币")}: ", style: TextStyle(height: 1.5)),
+              ],
+            ),
+            Column(
+              crossAxisAlignment: CrossAxisAlignment.end,
+              children: <Widget>[
+                userInfo == null
+                    ? Text(I18N.of("用户名"))
+                    : Text(userInfo["userName"].toString(),
+                        style: TextStyle(height: 1.5)),
+                userInfo == null
+                    ? Text(I18N.of("生日"))
+                    : Text(userInfo["birthday"].toString(),
+                        style: TextStyle(height: 1.5)),
+                userInfo == null
+                    ? Text(I18N.of("性别"))
+                    : Text(I18N.of(userInfo["gender"].toString()),
+                        style: TextStyle(height: 1.5)),
+                Text(coins.toString(), style: TextStyle(height: 1.5)),
+              ],
+            ),
+          ],
+        ),
+        Divider(),
+        Row(
+          mainAxisAlignment: MainAxisAlignment.end,
+          children: <Widget>[
+            ElevatedButton(
+              style: ButtonStyle(
+                  backgroundColor: MaterialStateProperty.all(isFollowed
+                      ? Theme.of(context).unselectedWidgetColor
+                      : Theme.of(context).colorScheme.secondary),
+                  foregroundColor:
+                      MaterialStateProperty.all(Theme.of(context).cardColor),
+                  shape: MaterialStateProperty.all(RoundedRectangleBorder(
+                    borderRadius: BorderRadius.circular(5.0),
+                  ))),
+              child: isFollowed ? Text(I18N.of("取消关注")) : Text(I18N.of("关注")),
+              onPressed: () async {
+                UserProvider userProvider =
+                    Provider.of<UserProvider>(context, listen: false);
+                String res = await Repository.getInstance().follow(
+                    context, userProvider.token, userProvider.uid, widget.uid);
+                if (res != null) {
+                  isFollowed = !isFollowed;
+                  setState(() {});
+                  await PopMenus.attention(
+                      context: context,
+                      content: Text(res == "followed"
+                          ? I18N.of("关注成功")
+                          : I18N.of("取消关注成功")));
+                  Navigator.of(context).pop(res);
+                }
+              },
+            ),
+//            ElevatedButton(
+//              color: Theme.of(context).colorScheme.secondary,
+//              textColor: Theme.of(context).cardColor,
+//              child: Text(I18N.of("私信")),
+//              onPressed: () {},
+//            ),
+          ],
+        ),
+      ],
+    );
+  }
+}

+ 84 - 0
lib/common/components/UserListTile.dart

@@ -0,0 +1,84 @@
+
+import 'package:flutter/material.dart';
+import 'package:flutter_habit/common/I18N.dart';
+import 'package:flutter_habit/common/provider/UserProvider.dart';
+import 'package:flutter_habit/network/Repository.dart';
+import 'package:provider/provider.dart';
+
+
+class UserListTile extends StatefulWidget {
+  final int uid;
+  final Widget trailing;
+  final Function onPress;
+
+  UserListTile({@required this.uid, this.onPress, this.trailing});
+
+  @override
+  _UserListTileState createState() => _UserListTileState();
+}
+
+class _UserListTileState extends State<UserListTile> {
+  List<int> photo;
+  Map userInfo;
+  int coins;
+
+  @override
+  void initState() {
+    
+    super.initState();
+    photo = null;
+    userInfo = {};
+    coins = -1;
+    getData();
+  }
+
+  Future<void> getData() async {
+    UserProvider userProvider =
+        Provider.of<UserProvider>(context, listen: false);
+    if (widget.uid == userProvider.uid) {
+      userInfo["userName"] = userProvider.userName;
+      photo = userProvider.photo;
+      coins = userProvider.coins;
+    } else {
+      userInfo = await Repository.getInstance().getUserInfo(widget.uid);
+      setState(() {});
+      coins = await Repository.getInstance().getCoin(widget.uid);
+      setState(() {});
+      photo = await Repository.getInstance().getPhoto(widget.uid);
+    }
+    setState(() {});
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    double previewSize = 50;
+    return ListTile(
+      leading: photo == null || photo.isEmpty
+          ? Icon(
+              Icons.account_circle,
+              size: previewSize,
+              color: Theme.of(context).unselectedWidgetColor,
+            )
+          : ClipOval(
+              child: Image.memory(
+                photo,
+                width: previewSize,
+                height: previewSize,
+              ),
+            ),
+      title: userInfo == null
+          ? Text(I18N.of("用户名"))
+          : Text(
+              userInfo["userName"].toString(),
+              style: widget.uid !=
+                  Provider.of<UserProvider>(context, listen: false).uid ? null : TextStyle(
+                decoration: TextDecoration.underline,
+                      color: Theme.of(context).colorScheme.secondary,
+              ),
+            ),
+      subtitle: Text("${I18N.of("金币")} : ${coins.toString()}"),
+      trailing: widget.trailing ?? Icon(Icons.info_outline),
+      onTap: Provider.of<UserProvider>(context, listen: false).token == null ? null:  widget.onPress,
+    );
+  }
+}

+ 95 - 0
lib/common/provider/ConfigProvider.dart

@@ -0,0 +1,95 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_habit/common/LocalData.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+class ConfigProvider extends ChangeNotifier {
+  int getUpTimeStart;
+  int getUpTimeEnd;
+
+  int breakfastTimeStart;
+  int breakfastTimeEnd;
+
+  int lunchTimeStart;
+  int lunchTimeEnd;
+
+  int midRestTimeStart;
+  int midRestTimeEnd;
+
+  int dinnerTimeStart;
+  int dinnerTimeEnd;
+
+  int restTimeStart;
+  int restTimeEnd;
+
+  void init() {
+    load();
+    store();
+
+    debugPrint("""init ConfigProvider to:
+      getUpTimeStart = ${DateTime.fromMillisecondsSinceEpoch(getUpTimeStart).toString().substring(11, 16)}
+      getUpTimeEnd = ${DateTime.fromMillisecondsSinceEpoch(getUpTimeEnd).toString().substring(11, 16)}
+      
+      breakfastTimeStart = ${DateTime.fromMillisecondsSinceEpoch(breakfastTimeStart).toString().substring(11, 16)}
+      breakfastTimeEnd = ${DateTime.fromMillisecondsSinceEpoch(breakfastTimeEnd).toString().substring(11, 16)}
+      
+      midRestTimeStart = ${DateTime.fromMillisecondsSinceEpoch(midRestTimeStart).toString().substring(11, 16)}
+      midRestTimeEnd = ${DateTime.fromMillisecondsSinceEpoch(midRestTimeEnd).toString().substring(11, 16)}
+      
+      lunchTimeStart = ${DateTime.fromMillisecondsSinceEpoch(lunchTimeStart).toString().substring(11, 16)}
+      lunchTimeEnd = ${DateTime.fromMillisecondsSinceEpoch(lunchTimeEnd).toString().substring(11, 16)}
+      
+      dinnerTimeStart = ${DateTime.fromMillisecondsSinceEpoch(dinnerTimeStart).toString().substring(11, 16)}
+      dinnerTimeEnd = ${DateTime.fromMillisecondsSinceEpoch(dinnerTimeEnd).toString().substring(11, 16)}
+      
+      restTimeStart = ${DateTime.fromMillisecondsSinceEpoch(restTimeStart).toString().substring(11, 16)}
+      restTimeEnd = ${DateTime.fromMillisecondsSinceEpoch(restTimeEnd).toString().substring(11, 16)}
+      """);
+  }
+
+  void load() {
+    SharedPreferences sp = LocalData.getInstance();
+    getUpTimeStart = sp.getInt("getUpTimeStart") ?? DateTime(1,1,1,6).millisecondsSinceEpoch;
+    getUpTimeEnd = sp.getInt("getUpTimeEnd") ?? DateTime(1,1,1,7).millisecondsSinceEpoch;
+
+    breakfastTimeStart = sp.getInt("breakfastTimeStart") ?? DateTime(1,1,1,7).millisecondsSinceEpoch;
+    breakfastTimeEnd = sp.getInt("breakfastTimeEnd") ?? DateTime(1,1,1,8).millisecondsSinceEpoch;
+
+    lunchTimeStart = sp.getInt("lunchTimeStart") ?? DateTime(1,1,1,12).millisecondsSinceEpoch;
+    lunchTimeEnd = sp.getInt("lunchTimeEnd") ?? DateTime(1,1,1,13).millisecondsSinceEpoch;
+
+    midRestTimeStart = sp.getInt("midRestTimeStart") ?? DateTime(1,1,1,13).millisecondsSinceEpoch;
+    midRestTimeEnd = sp.getInt("midRestTimeEnd") ?? DateTime(1,1,1,14).millisecondsSinceEpoch;
+
+    dinnerTimeStart = sp.getInt("dinnerTimeStart") ?? DateTime(1,1,1,17).millisecondsSinceEpoch;
+    dinnerTimeEnd = sp.getInt("dinnerTimeEnd") ?? DateTime(1,1,1,18).millisecondsSinceEpoch;
+
+    restTimeStart = sp.getInt("restTimeStart") ?? DateTime(1,1,1,21).millisecondsSinceEpoch;
+    restTimeEnd = sp.getInt("restTimeEnd") ?? DateTime(1,1,1,22).millisecondsSinceEpoch;
+  }
+
+  void store() {
+    SharedPreferences sp = LocalData.getInstance();
+    sp.setInt("getUpTimeStart", getUpTimeStart);
+    sp.setInt("getUpTimeEnd", getUpTimeEnd);
+
+    sp.setInt("breakfastTimeStart", breakfastTimeStart);
+    sp.setInt("breakfastTimeEnd", breakfastTimeEnd);
+
+    sp.setInt("lunchTimeStart", lunchTimeStart);
+    sp.setInt("lunchTimeEnd", lunchTimeEnd);
+
+    sp.setInt("midRestTimeStart", midRestTimeStart);
+    sp.setInt("midRestTimeEnd", midRestTimeEnd);
+
+    sp.setInt("dinnerTimeStart", dinnerTimeStart);
+    sp.setInt("dinnerTimeEnd", dinnerTimeEnd);
+
+    sp.setInt("restTimeStart", restTimeStart);
+    sp.setInt("restTimeEnd", restTimeEnd);
+  }
+
+  void refresh() {
+    notifyListeners();
+  }
+
+}

+ 505 - 0
lib/common/provider/DataProvider.dart

@@ -0,0 +1,505 @@
+import 'dart:async';
+
+import 'package:fl_chart/fl_chart.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_habit/common/provider/ConfigProvider.dart';
+import 'package:flutter_habit/common/utils/ConvertUtils.dart';
+import 'package:flutter_habit/common/utils/VerificationUtils.dart';
+import 'package:flutter_habit/database/entity/BasicInfo.dart';
+import 'package:flutter_habit/database/entity/ExerciseInfo.dart';
+import 'package:flutter_habit/database/entity/FoodInfo.dart';
+import 'package:flutter_habit/database/entity/LifeInfo.dart';
+import 'package:flutter_habit/database/entity/ScheduledExercise.dart';
+import 'package:flutter_habit/database/entity/SportInfo.dart';
+import 'package:flutter_habit/database/entity/StudyInfo.dart';
+import 'package:flutter_habit/database/mapper/BasicInfoMapper.dart';
+import 'package:flutter_habit/database/mapper/ExerciseInfoMapper.dart';
+import 'package:flutter_habit/database/mapper/FoodInfoMapper.dart';
+import 'package:flutter_habit/database/mapper/LifeInfoMapper.dart';
+import 'package:flutter_habit/database/mapper/ScheduledExerciseMapper.dart';
+import 'package:flutter_habit/database/mapper/SportInfoMapper.dart';
+import 'package:flutter_habit/database/mapper/StudyInfoMapper.dart';
+
+class DataProvider extends ChangeNotifier {
+  Future<void> init() async {
+    await loadData();
+
+    DateTime now = DateTime.now();
+    Timer.periodic(
+        ConvertUtils.dateOfDateTime(now)
+            .add(Duration(days: 1, seconds: 1))
+            .difference(now), (t) async {
+      t.cancel();
+      debugPrint("refresh");
+      await loadData();
+      Timer.periodic(Duration(days: 1), (t) async {
+        debugPrint("refresh");
+        await loadData();
+      });
+    });
+
+    debugPrint("init DataProvider");
+  }
+
+  Future<void> loadData() async {
+    await loadBasicInfoData();
+    await loadLifeInfoData();
+    await loadExerciseInfoData();
+    await loadStudyInfoData();
+    await evaluateToday();
+  }
+
+  // basic info
+  double height;
+  double weight;
+  String bmi;
+
+  double breastLine;
+  double waistLine;
+  double hipLine;
+
+  List<FlSpot> weightFlSpots = [];
+  int weightChartSize = 7;
+
+  List<FlSpot> brestLineFlSpots = [];
+  List<FlSpot> waistLineFlSpots = [];
+  List<FlSpot> hipLineFlSpots = [];
+  int bwhChartSize = 7;
+
+  Future<void> loadBasicInfoData() async {
+    int dateTime90daysAgo = ConvertUtils.dateOfDateTime(DateTime.now())
+        .subtract(Duration(days: 90))
+        .millisecondsSinceEpoch;
+    // 基本信息
+    List<BasicInfo> basicInfoList =
+        await BasicInfoMapper().selectWhere("date >= $dateTime90daysAgo");
+    height = null;
+    weight = null;
+    bmi = null;
+    breastLine = null;
+    waistLine = null;
+    hipLine = null;
+    // 基本数据
+    if (basicInfoList.isNotEmpty) {
+      height = basicInfoList.last.getHeight();
+      weight = basicInfoList.last.getWeight();
+      bmi = (weight / height / height * 10000).toStringAsFixed(2);
+      breastLine = basicInfoList.last.getBreastLine();
+      waistLine = basicInfoList.last.getWaistLine();
+      hipLine = basicInfoList.last.getHipLine();
+    }
+    // 将数据转为点
+    weightFlSpots = [];
+
+    brestLineFlSpots = [];
+    waistLineFlSpots = [];
+    hipLineFlSpots = [];
+    basicInfoList.forEach((i) {
+      // 换算为x.x天
+      weightFlSpots.add(
+        FlSpot(
+          ConvertUtils.localDaysSinceEpoch(
+                  DateTime.fromMillisecondsSinceEpoch(i.getDate()))
+              .floorToDouble(),
+          i.getWeight(),
+        ),
+      );
+      brestLineFlSpots.add(
+        FlSpot(
+          ConvertUtils.localDaysSinceEpoch(
+                  DateTime.fromMillisecondsSinceEpoch(i.getDate()))
+              .floorToDouble(),
+          i.getBreastLine(),
+        ),
+      );
+      waistLineFlSpots.add(
+        FlSpot(
+          ConvertUtils.localDaysSinceEpoch(
+                  DateTime.fromMillisecondsSinceEpoch(i.getDate()))
+              .floorToDouble(),
+          i.getWaistLine(),
+        ),
+      );
+      hipLineFlSpots.add(
+        FlSpot(
+          ConvertUtils.localDaysSinceEpoch(
+                  DateTime.fromMillisecondsSinceEpoch(i.getDate()))
+              .floorToDouble(),
+          i.getHipLine(),
+        ),
+      );
+    });
+
+    notifyListeners();
+  }
+
+  // life info
+  int lastNightSleepTime;
+  double todayInjectKCal = 0;
+  int todayProgress = 0;
+  double todayMoney = 0;
+
+  List<FlSpot> sleepTimeFlSpots = [];
+  int sleepTimeChartSize = 7;
+
+  List<FlSpot> injectKCalFlSpots = [];
+  int injectKCalChartSize = 7;
+
+  List<FlSpot> getUpTimeFlSpots = [];
+  List<FlSpot> midRestTimeFlSpots = [];
+  List<FlSpot> restTimeFlSpots = [];
+  int timeChartSize = 7;
+
+  List<FlSpot> progressFlSpots = [];
+  int progressChartSize = 7;
+
+  List<FlSpot> moneyFlSpots = [];
+  int moneyChartSize = 7;
+
+  List<FlSpot> eatBreakfastTimeFlSpots = [];
+  List<FlSpot> eatLunchTimeFlSpots = [];
+  List<FlSpot> eatDinnerTimeFlSpots = [];
+  int eatTimeChartSize = 7;
+
+  Future<void> loadLifeInfoData() async {
+    int date91daysAgo = ConvertUtils.dateOfDateTime(DateTime.now())
+        .subtract(Duration(days: 91))
+        .millisecondsSinceEpoch;
+    // 日常生活
+    List<LifeInfo> lifeInfoList =
+        await LifeInfoMapper().selectWhere("date >= $date91daysAgo");
+    List<FoodInfo> foodInfoList = await FoodInfoMapper().selectAll();
+
+    // 构造点、数据
+    sleepTimeFlSpots = [];
+    injectKCalFlSpots = [];
+    progressFlSpots = [];
+    getUpTimeFlSpots = [];
+    midRestTimeFlSpots = [];
+    restTimeFlSpots = [];
+    moneyFlSpots = [];
+    eatBreakfastTimeFlSpots = [];
+    eatLunchTimeFlSpots = [];
+    eatDinnerTimeFlSpots = [];
+    todayInjectKCal = 0;
+    todayProgress = 0;
+    todayMoney = 0;
+    lastNightSleepTime = null;
+    if (lifeInfoList.isNotEmpty) {
+      for (int i = 0; i < lifeInfoList.length; i++) {
+        LifeInfo lastLifeInfo = i == 0 ? LifeInfo() : lifeInfoList[i - 1];
+        LifeInfo currLifeInfo = lifeInfoList[i];
+        // 准备数据
+        int date = currLifeInfo.getDate();
+        int lastDate = lastLifeInfo.getDate();
+        int sleepTime;
+        if (lastLifeInfo.getRestTime() != null &&
+            currLifeInfo.getGetUpTime() != null) {
+          sleepTime = currLifeInfo.getGetUpTime() - lastLifeInfo.getRestTime();
+        }
+        lastNightSleepTime = sleepTime;
+        double totalCal = 0;
+        if (currLifeInfo.getBreakfastQuantity() != null) {
+          FoodInfo currBreakfastInfo = foodInfoList.firstWhere(
+              (test) => test.getId() == currLifeInfo.getBreakfastId());
+          totalCal += currBreakfastInfo.getHgkCalorie() *
+              currLifeInfo.getBreakfastQuantity();
+        }
+        if (currLifeInfo.getLunchQuantity() != null) {
+          FoodInfo currLunchInfo = foodInfoList
+              .firstWhere((test) => test.getId() == currLifeInfo.getLunchId());
+          totalCal +=
+              currLunchInfo.getHgkCalorie() * currLifeInfo.getLunchQuantity();
+        }
+        if (currLifeInfo.getDinnerQuantity() != null) {
+          FoodInfo currDinnerInfo = foodInfoList
+              .firstWhere((test) => test.getId() == currLifeInfo.getDinnerId());
+          totalCal +=
+              currDinnerInfo.getHgkCalorie() * currLifeInfo.getDinnerQuantity();
+        }
+        todayInjectKCal = totalCal;
+        int progress = 0;
+        ConfigProvider configProvider = ConfigProvider();
+        configProvider.load();
+        progress += currLifeInfo.getGetUpTime() == null
+            ? 0
+            : VerifyUtils.isBetweenTime(configProvider.getUpTimeStart,
+                    currLifeInfo.getGetUpTime(), configProvider.getUpTimeEnd)
+                ? 2
+                : 1;
+        progress += currLifeInfo.getBreakfastTime() == null
+            ? 0
+            : VerifyUtils.isBetweenTime(
+                    configProvider.breakfastTimeStart,
+                    currLifeInfo.getBreakfastTime(),
+                    configProvider.breakfastTimeEnd)
+                ? 2
+                : 1;
+        progress += currLifeInfo.getLunchTime() == null
+            ? 0
+            : VerifyUtils.isBetweenTime(configProvider.lunchTimeStart,
+                    currLifeInfo.getLunchTime(), configProvider.lunchTimeEnd)
+                ? 2
+                : 1;
+        progress += currLifeInfo.getMidRestTime() == null
+            ? 0
+            : VerifyUtils.isBetweenTime(
+                    configProvider.midRestTimeStart,
+                    currLifeInfo.getMidRestTime(),
+                    configProvider.midRestTimeEnd)
+                ? 2
+                : 1;
+        progress += currLifeInfo.getDinnerTime() == null
+            ? 0
+            : VerifyUtils.isBetweenTime(configProvider.dinnerTimeStart,
+                    currLifeInfo.getDinnerTime(), configProvider.dinnerTimeEnd)
+                ? 2
+                : 1;
+        progress += currLifeInfo.getRestTime() == null
+            ? 0
+            : VerifyUtils.isBetweenTime(configProvider.restTimeStart,
+                    currLifeInfo.getRestTime(), configProvider.restTimeEnd)
+                ? 2
+                : 1;
+        todayProgress = progress;
+        // 构造点
+        if (lastDate != null && lastNightSleepTime != null) {
+          sleepTimeFlSpots.add(FlSpot(
+              ConvertUtils.localDaysSinceEpoch(
+                      DateTime.fromMillisecondsSinceEpoch(lastDate))
+                  .floorToDouble(),
+              ConvertUtils.fixedDouble(
+                  ConvertUtils.hourFormMilliseconds(lastNightSleepTime), 2)));
+        }
+        injectKCalFlSpots.add(FlSpot(
+            ConvertUtils.localDaysSinceEpoch(
+                    DateTime.fromMillisecondsSinceEpoch(date))
+                .floorToDouble(),
+            ConvertUtils.fixedDouble(totalCal, 2)));
+
+        progressFlSpots.add(FlSpot(
+            ConvertUtils.localDaysSinceEpoch(
+                    DateTime.fromMillisecondsSinceEpoch(date))
+                .floorToDouble(),
+            ConvertUtils.fixedDouble(progress / 12, 2)));
+
+        if (currLifeInfo.getGetUpTime() != null) {
+          getUpTimeFlSpots.add(FlSpot(
+              ConvertUtils.localDaysSinceEpoch(
+                      DateTime.fromMillisecondsSinceEpoch(date))
+                  .floorToDouble(),
+              24 -
+                  ConvertUtils.hourFormMillisecondsSinceEpoch(
+                      currLifeInfo.getGetUpTime())));
+        }
+        if (currLifeInfo.getMidRestTime() != null) {
+          midRestTimeFlSpots.add(FlSpot(
+              ConvertUtils.localDaysSinceEpoch(
+                      DateTime.fromMillisecondsSinceEpoch(date))
+                  .floorToDouble(),
+              24 -
+                  ConvertUtils.hourFormMillisecondsSinceEpoch(
+                      currLifeInfo.getMidRestTime())));
+        }
+        if (currLifeInfo.getRestTime() != null) {
+          restTimeFlSpots.add(FlSpot(
+              ConvertUtils.localDaysSinceEpoch(
+                      DateTime.fromMillisecondsSinceEpoch(date))
+                  .floorToDouble(),
+              24 -
+                  ConvertUtils.hourFormMillisecondsSinceEpoch(
+                      currLifeInfo.getRestTime())));
+        }
+
+        if (currLifeInfo.getBreakfastTime() != null) {
+          eatBreakfastTimeFlSpots.add(FlSpot(
+              ConvertUtils.localDaysSinceEpoch(
+                      DateTime.fromMillisecondsSinceEpoch(date))
+                  .floorToDouble(),
+              24 -
+                  ConvertUtils.hourFormMillisecondsSinceEpoch(
+                      currLifeInfo.getBreakfastTime())));
+        }
+        if (currLifeInfo.getLunchTime() != null) {
+          eatLunchTimeFlSpots.add(FlSpot(
+              ConvertUtils.localDaysSinceEpoch(
+                      DateTime.fromMillisecondsSinceEpoch(date))
+                  .floorToDouble(),
+              24 -
+                  ConvertUtils.hourFormMillisecondsSinceEpoch(
+                      currLifeInfo.getLunchTime())));
+        }
+        if (currLifeInfo.getDinnerTime() != null) {
+          eatDinnerTimeFlSpots.add(FlSpot(
+              ConvertUtils.localDaysSinceEpoch(
+                      DateTime.fromMillisecondsSinceEpoch(date))
+                  .floorToDouble(),
+              24 -
+                  ConvertUtils.hourFormMillisecondsSinceEpoch(
+                      currLifeInfo.getDinnerTime())));
+        }
+
+        double totalMoney = 0;
+        totalMoney += currLifeInfo.getBreakfastMoney() ?? 0;
+        totalMoney += currLifeInfo.getLunchMoney() ?? 0;
+        totalMoney += currLifeInfo.getDinnerMoney() ?? 0;
+        moneyFlSpots.add(FlSpot(
+            ConvertUtils.localDaysSinceEpoch(
+                    DateTime.fromMillisecondsSinceEpoch(date))
+                .floorToDouble(),
+            ConvertUtils.fixedDouble(totalMoney, 2)));
+        todayMoney = totalMoney;
+      }
+    }
+    notifyListeners();
+  }
+
+  int sevenDayExerciseTimes = 0;
+  double sevenDayExerciseTotalKCal = 0;
+  int scheduledExerciseCount = 0;
+
+  List<MapEntry<ScheduledExercise, SportInfo>> scheduledExerciseSportInfoList =
+      [];
+
+  List<FlSpot> exerciseInfoFlSpots = [];
+  int exerciseInfoChartSize = 7;
+
+  Future<void> loadExerciseInfoData() async {
+    int date90daysAgo = ConvertUtils.dateOfDateTime(DateTime.now())
+        .subtract(Duration(days: 90))
+        .millisecondsSinceEpoch;
+    int date7daysAgo = ConvertUtils.dateOfDateTime(DateTime.now())
+        .subtract(Duration(days: 7))
+        .millisecondsSinceEpoch;
+    List<ExerciseInfo> exerciseInfoList = await ExerciseInfoMapper()
+        .selectWhere("exerciseTime >= $date90daysAgo");
+    List<ExerciseInfo> sevenDayExerciseInfoList =
+        await ExerciseInfoMapper().selectWhere("exerciseTime >= $date7daysAgo");
+    List<ScheduledExercise> schedules =
+        await ScheduledExerciseMapper().selectAll();
+    List<SportInfo> sports = await SportInfoMapper().selectAll();
+    scheduledExerciseSportInfoList = schedules.map((i) {
+      return MapEntry(
+          i, sports.firstWhere((test) => test.getId() == i.getSportId()));
+    }).toList();
+    // 卡
+    scheduledExerciseCount = schedules.length;
+    sevenDayExerciseTimes = sevenDayExerciseInfoList.length;
+    sevenDayExerciseTotalKCal = 0;
+    sevenDayExerciseInfoList.forEach((i) {
+      sevenDayExerciseTotalKCal += i.getExerciseQuantity() *
+          sports
+              .firstWhere((test) => test.getId() == i.getSportId())
+              .getHkCalorie();
+    });
+    // 表
+    exerciseInfoFlSpots = [];
+    Map<int, double> temp = {};
+    exerciseInfoList.forEach((i) {
+      int currDay = ConvertUtils.localDaysSinceEpoch(
+              DateTime.fromMillisecondsSinceEpoch(i.getExerciseTime()))
+          .floor();
+      if (temp[currDay] == null) {
+        temp[currDay] = i.getExerciseQuantity() *
+            sports
+                .firstWhere((test) => test.getId() == i.getSportId())
+                .getHkCalorie();
+      } else {
+        temp[currDay] += i.getExerciseQuantity() *
+            sports
+                .firstWhere((test) => test.getId() == i.getSportId())
+                .getHkCalorie();
+      }
+    });
+    temp.forEach((k, v) {
+      exerciseInfoFlSpots
+          .add(FlSpot(k.toDouble(), ConvertUtils.fixedDouble(v, 2)));
+    });
+    notifyListeners();
+  }
+
+  int lateTimes = 0;
+  int absentTimes = 0;
+  int unSolveTroubles = 0;
+  int unDoneHomeWorks = 0;
+  List<StudyInfo> unSolveStudyInfoList = [];
+
+  List<FlSpot> dailyStudyCountFlSpots = [];
+  int dailyStudyCountChartSize = 7;
+
+  Future<void> loadStudyInfoData() async {
+    int date90daysAgo = ConvertUtils.dateOfDateTime(DateTime.now())
+        .subtract(Duration(days: 90))
+        .millisecondsSinceEpoch;
+    List<StudyInfo> studyInfoList =
+        await StudyInfoMapper().selectWhere("date >= $date90daysAgo");
+    List<StudyInfo> lateList =
+        await StudyInfoMapper().selectWhere("isLate = 1");
+    List<StudyInfo> absentList =
+        await StudyInfoMapper().selectWhere("isAbsent = 1");
+    List<StudyInfo> unSolveTroublesAndUnDoneHomeList = await StudyInfoMapper()
+        .selectWhere("isTroublesSolved = 0 or isHomeWorkDone == 0");
+    lateTimes = 0;
+    absentTimes = 0;
+    unSolveTroubles = 0;
+    unDoneHomeWorks = 0;
+    lateTimes = lateList.length;
+    absentTimes = absentList.length;
+    unSolveStudyInfoList = [];
+    unSolveTroubles = 0;
+    unDoneHomeWorks = 0;
+    unSolveTroublesAndUnDoneHomeList.forEach((i) {
+      unSolveStudyInfoList.add(i);
+      if (i.getIsHomeWorkDone() == 0) {
+        unDoneHomeWorks++;
+      }
+      if (i.getIsTroublesSolved() == 0) {
+        unSolveTroubles++;
+      }
+    });
+
+    dailyStudyCountFlSpots = [];
+
+    Map<int, int> temp = {};
+    studyInfoList.forEach((i) {
+      int currDay = ConvertUtils.localDaysSinceEpoch(
+              DateTime.fromMillisecondsSinceEpoch(i.getDate()))
+          .floor();
+      if (temp[currDay] == null) {
+        temp[currDay] = 1;
+      } else {
+        temp[currDay] += 1;
+      }
+    });
+    temp.forEach((key, value) {
+      dailyStudyCountFlSpots.add(FlSpot(key.toDouble(), value.toDouble()));
+    });
+    notifyListeners();
+  }
+
+  int todayEvaluate = 0;
+
+  Future<void> evaluateToday() async {
+    // 0 - 15
+    todayEvaluate = 0;
+    todayEvaluate += todayProgress;
+    int todayZeroTime =
+        ConvertUtils.dateOfDateTime(DateTime.now()).millisecondsSinceEpoch;
+    List<BasicInfo> l1 =
+        await BasicInfoMapper().selectWhere("date >= $todayZeroTime");
+    if (l1.isNotEmpty) {
+      todayEvaluate++;
+    }
+    List<ExerciseInfo> l2 =
+    await ExerciseInfoMapper().selectWhere("exerciseTime >= $todayZeroTime");
+    if (l2.isNotEmpty) {
+      todayEvaluate++;
+    }
+    List<StudyInfo> l3 =
+    await StudyInfoMapper().selectWhere("date >= $todayZeroTime");
+    if (l3.isNotEmpty) {
+      todayEvaluate++;
+    }
+    notifyListeners();
+  }
+}

+ 200 - 0
lib/common/provider/NotificationProvider.dart

@@ -0,0 +1,200 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_local_notifications/flutter_local_notifications.dart';
+import 'package:flutter_habit/common/I18N.dart';
+import 'package:flutter_habit/common/LocalData.dart';
+import 'package:flutter_habit/common/provider/ConfigProvider.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+class NotificationProvider extends ChangeNotifier {
+  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
+
+  bool getUpNotification;
+  bool breakfastNotification;
+  bool lunchNotification;
+  bool midRestNotification;
+  bool dinnerNotification;
+  bool restNotification;
+
+  Future<void> init() async {
+    await initFlutterLocalNotificationsPlugin();
+    load();
+    store();
+    await startScheduledTasks();
+  }
+
+  Future<void> initFlutterLocalNotificationsPlugin() async {
+    if (flutterLocalNotificationsPlugin == null) {
+      flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
+      // initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project
+      AndroidInitializationSettings initializationSettingsAndroid =
+          AndroidInitializationSettings("logo");
+      DarwinInitializationSettings initializationSettingsIOS =
+          DarwinInitializationSettings();
+      InitializationSettings initializationSettings = InitializationSettings(
+          android: initializationSettingsAndroid,
+          iOS: initializationSettingsIOS);
+      flutterLocalNotificationsPlugin.initialize(initializationSettings);
+      debugPrint("init NotificationsUtils");
+    }
+  }
+
+  Future<void> cancelScheduledTasks() async {
+    await cancel(1);
+    await cancel(2);
+    await cancel(3);
+    await cancel(4);
+    await cancel(5);
+    await cancel(6);
+    debugPrint("cancelScheduledTasks");
+  }
+
+  Future<List<PendingNotificationRequest>> getNotifications() async {
+    List pendingNotificationRequests =
+        await flutterLocalNotificationsPlugin.pendingNotificationRequests();
+    return pendingNotificationRequests;
+  }
+
+  Future<void> startScheduledTasks() async {
+    await cancelScheduledTasks();
+    ConfigProvider configProvider = ConfigProvider();
+    configProvider.load();
+    if (getUpNotification) {
+      DateTime dateTime =
+          DateTime.fromMillisecondsSinceEpoch(configProvider.getUpTimeStart);
+      await scheduledNotice(
+        1,
+        "Habit ${I18N.of("打卡提醒")}",
+        I18N.of("起床打卡开始了"),
+        dateTime.hour,
+        dateTime.minute,
+        dateTime.second,
+      );
+    }
+    if (breakfastNotification) {
+      DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(
+          configProvider.breakfastTimeStart);
+      await scheduledNotice(
+        2,
+        "Habit ${I18N.of("打卡提醒")}",
+        I18N.of("早饭打卡开始了"),
+        dateTime.hour,
+        dateTime.minute,
+        dateTime.second,
+      );
+    }
+    if (lunchNotification) {
+      DateTime dateTime =
+          DateTime.fromMillisecondsSinceEpoch(configProvider.lunchTimeStart);
+      await scheduledNotice(
+        3,
+        "Habit ${I18N.of("打卡提醒")}",
+        I18N.of("午饭打卡开始了"),
+        dateTime.hour,
+        dateTime.minute,
+        dateTime.second,
+      );
+    }
+    if (midRestNotification) {
+      DateTime dateTime =
+          DateTime.fromMillisecondsSinceEpoch(configProvider.midRestTimeStart);
+      await scheduledNotice(
+        4,
+        "Habit ${I18N.of("打卡提醒")}",
+        I18N.of("午休打卡开始了"),
+        dateTime.hour,
+        dateTime.minute,
+        dateTime.second,
+      );
+    }
+    if (dinnerNotification) {
+      DateTime dateTime =
+          DateTime.fromMillisecondsSinceEpoch(configProvider.dinnerTimeStart);
+      await scheduledNotice(
+        5,
+        "Habit ${I18N.of("打卡提醒")}",
+        I18N.of("起床打卡开始了"),
+        dateTime.hour,
+        dateTime.minute,
+        dateTime.second,
+      );
+    }
+    if (restNotification) {
+      DateTime dateTime =
+          DateTime.fromMillisecondsSinceEpoch(configProvider.restTimeStart);
+      await scheduledNotice(
+        6,
+        "Habit ${I18N.of("打卡提醒")}",
+        I18N.of("睡觉打卡开始了"),
+        dateTime.hour,
+        dateTime.minute,
+        dateTime.second,
+      );
+    }
+    debugPrint("startScheduledTasks");
+  }
+
+  Future<void> scheduledNotice(int id, String title, String body, int hour,
+      int minute, int second) async {
+    // Time time = Time(hour, minute, second);
+    AndroidNotificationDetails androidPlatformChannelSpecifics =
+        AndroidNotificationDetails(id.toString(), title,
+            channelDescription: body);
+    DarwinNotificationDetails iOSPlatformChannelSpecifics =
+        DarwinNotificationDetails();
+
+    NotificationDetails platformChannelSpecifics = NotificationDetails(
+        android: androidPlatformChannelSpecifics,
+        iOS: iOSPlatformChannelSpecifics);
+
+    await flutterLocalNotificationsPlugin.show(
+        id, title, body, platformChannelSpecifics);
+    debugPrint("scheduledNotice $hour $minute $second");
+  }
+
+  Future<void> notice(int id, String title, String body) async {
+    var androidPlatformChannelSpecifics = AndroidNotificationDetails(
+        id.toString(), title,
+        channelDescription: body,
+        importance: Importance.max,
+        priority: Priority.high,
+        ticker: title);
+    var iOSPlatformChannelSpecifics = DarwinNotificationDetails();
+
+    var platformChannelSpecifics = NotificationDetails(
+        android: androidPlatformChannelSpecifics,
+        iOS: iOSPlatformChannelSpecifics);
+
+    await flutterLocalNotificationsPlugin.show(
+        id, title, body, platformChannelSpecifics);
+  }
+
+  Future<void> cancel(int id) async {
+    await flutterLocalNotificationsPlugin.cancel(id);
+  }
+
+  void load() {
+    SharedPreferences sp = LocalData.getInstance();
+    getUpNotification = sp.getBool("getUpNotification") ?? true;
+    breakfastNotification = sp.getBool("breakfastNotification") ?? true;
+    lunchNotification = sp.getBool("lunchNotification") ?? true;
+    midRestNotification = sp.getBool("midRestNotification") ?? true;
+    dinnerNotification = sp.getBool("dinnerNotification") ?? true;
+    restNotification = sp.getBool("restNotification") ?? true;
+  }
+
+  void store() {
+    SharedPreferences sp = LocalData.getInstance();
+    sp.setBool("getUpNotification", getUpNotification);
+    sp.setBool("breakfastNotification", breakfastNotification);
+    sp.setBool("lunchNotification", lunchNotification);
+    sp.setBool("midRestNotification", midRestNotification);
+    sp.setBool("dinnerNotification", dinnerNotification);
+    sp.setBool("restNotification", restNotification);
+  }
+
+  Future<void> refresh() async {
+    store();
+    await startScheduledTasks();
+    notifyListeners();
+  }
+}

+ 58 - 0
lib/common/provider/ThemeProvider.dart

@@ -0,0 +1,58 @@
+import 'package:flutter/material.dart';
+
+import '../LocalData.dart';
+
+class ThemeProvider extends ChangeNotifier {
+  int currentIndex;
+
+  MaterialColor currentMaterialColor;
+
+  Brightness currentBrightness;
+
+  List<Color> otherColors;
+
+  ThemeProvider() {
+    currentIndex = 0;
+    currentMaterialColor = Colors.amber;
+    currentBrightness = Brightness.light;
+    otherColors = [];
+  }
+
+  void init() {
+    int index = LocalData.getInstance().getInt("theme");
+    if (index == null) {
+      index = 0;
+      LocalData.getInstance().setInt("theme", index);
+    }
+    changeTheme(index);
+    debugPrint(
+        "init ThemeProvider to: $currentMaterialColor, $currentBrightness");
+  }
+
+  void changeTheme(int index) {
+    if (index == themeColors.length) {
+      currentMaterialColor = Colors.teal;
+      currentBrightness = Brightness.dark;
+    } else {
+      currentMaterialColor = themeColors[index];
+      currentBrightness = Brightness.light;
+    }
+    currentIndex = index;
+    LocalData.getInstance().setInt("theme", index);
+    otherColors = List<Color>.of(themeColors);
+    otherColors.remove(currentMaterialColor);
+    notifyListeners();
+  }
+}
+
+
+
+List<MaterialColor> themeColors = [
+  Colors.amber,
+  Colors.pink,
+  Colors.green,
+  Colors.purple,
+  Colors.blue,
+  Colors.cyan,
+  Colors.brown,
+];

+ 82 - 0
lib/common/provider/UserProvider.dart

@@ -0,0 +1,82 @@
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_habit/common/LocalData.dart';
+import 'package:flutter_habit/view/HomePage.dart';
+
+class UserProvider extends ChangeNotifier {
+  String token;
+
+  int uid;
+  String email;
+  String userName;
+  String gender;
+  String birthday;
+  Uint8List photo;
+  int coins;
+
+  void init() {
+    load();
+    debugPrint("""init AccountProvider to:
+      token = $token
+      uid = $uid
+      email = $email
+      userName = $userName
+      gender = $gender
+      birthday = $birthday
+      photo = ${photo != null ? "notNull" : "null"}
+      coins = $coins""");
+  }
+
+  void store() {
+    LocalData.getInstance().setString("token", token);
+    LocalData.getInstance().setInt("uid", uid);
+    LocalData.getInstance().setString("email", email);
+    LocalData.getInstance().setString("userName", userName);
+    LocalData.getInstance().setString("gender", gender);
+    LocalData.getInstance().setString("birthday", birthday);
+    LocalData.getInstance().setString(
+        "photo", photo == null ? null : Base64Encoder().convert(photo));
+    LocalData.getInstance().setInt("coins", coins);
+  }
+
+  void load() {
+    token = LocalData.getInstance().getString("token");
+    uid = LocalData.getInstance().getInt("uid");
+    email = LocalData.getInstance().getString("email");
+    userName = LocalData.getInstance().getString("userName");
+    gender = LocalData.getInstance().getString("gender");
+    birthday = LocalData.getInstance().getString("birthday");
+    String listString = LocalData.getInstance().getString("photo");
+    if (listString == null) {
+      photo = null;
+    } else {
+      photo = Base64Decoder().convert(listString);
+    }
+    coins = LocalData.getInstance().getInt("coins");
+  }
+
+  Future<void> cleanDataAndBackToHome(BuildContext context) async {
+    token = null;
+
+    uid = null;
+//    email = null;
+    userName = null;
+    gender = null;
+    birthday = null;
+    photo = null;
+    coins = null;
+    store();
+    await Navigator.of(context).pushAndRemoveUntil(
+      MaterialPageRoute(builder: (context) => HomePage()),
+      (route) => route == null,
+    );
+    refresh();
+  }
+
+  void refresh() {
+    store();
+    notifyListeners();
+  }
+}

+ 54 - 0
lib/common/utils/ConvertUtils.dart

@@ -0,0 +1,54 @@
+import 'dart:math';
+
+import 'package:flutter_habit/common/I18N.dart';
+
+class ConvertUtils {
+  static int offset = DateTime.now().timeZoneOffset.inMilliseconds;
+
+  static String packString(Object s) {
+    return s == null ? I18N.of("无数据") : s.toString();
+  }
+
+  static String md5Encode(String s) {
+    // return md5.convert(Utf8Encoder().convert("ha${s}bit")).toString();
+    return "";
+  }
+
+  static double localDaysSinceEpoch(DateTime dateTime) {
+    return (dateTime.millisecondsSinceEpoch + offset) / 86400000;
+  }
+
+  static DateTime dateTimeOfLocalDaysSinceEpoch(double daysSinceEpoch) {
+    return DateTime.fromMillisecondsSinceEpoch(
+        (daysSinceEpoch * 86400000).round() - offset);
+  }
+
+  static DateTime dateOfDateTime(DateTime dateTime) {
+    return DateTime(dateTime.year, dateTime.month, dateTime.day);
+  }
+
+  static String timeFormMillisecondsSinceEpoch(int millisecondsSinceEpoch) {
+    return DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch)
+        .toString()
+        .substring(11, 16);
+  }
+
+  static double hourFormMilliseconds(int milliseconds) {
+    return milliseconds / 1000 / 60 / 60;
+  }
+
+  static double hourFormMillisecondsSinceEpoch(int millisecondsSinceEpoch) {
+    DateTime time = DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch);
+    return Duration(
+                hours: time.hour, minutes: time.minute, seconds: time.second)
+            .inMilliseconds /
+        1000 /
+        60 /
+        60;
+  }
+
+  static double fixedDouble(double value, int fix) {
+    int fixNum = pow(10, fix);
+    return (value * fixNum).round() / fixNum;
+  }
+}

+ 43 - 0
lib/common/utils/VerificationUtils.dart

@@ -0,0 +1,43 @@
+
+class VerifyUtils {
+  static final RegExp _regexUserName =
+      RegExp("^[\u4e00-\u9fa5a-zA-Z0-9]{2,8}\$");
+  static final RegExp _regexEmail = RegExp("^\\w+@\\w+(\.\\w+)+\$");
+  static final RegExp _regexPassword =
+      RegExp("^(?=.*[a-z])(?=.*[A-Z])[a-zA-Z0-9#?!@\$%^&*,]{8,16}\$");
+
+  static bool isUserName(String s) {
+    if (s == null || s.isEmpty) {
+      return false;
+    }
+    return _regexUserName.hasMatch(s);
+  }
+
+  static bool isEmail(String s) {
+    if (s == null || s.isEmpty) {
+      return false;
+    }
+    return _regexEmail.hasMatch(s);
+  }
+
+  static bool isPassword(String s) {
+    if (s == null || s.isEmpty) {
+      return false;
+    }
+    return _regexPassword.hasMatch(s);
+  }
+
+  static bool nowIsBetweenTime(int a, int b) {
+    DateTime now = DateTime.now();
+    DateTime nowTime = DateTime(1, 1, 1, now.hour, now.minute, now.second);
+    return a <= nowTime.millisecondsSinceEpoch &&
+        nowTime.millisecondsSinceEpoch <= b;
+  }
+
+  static bool isBetweenTime(int a, int v, int b) {
+    DateTime vTime = DateTime.fromMillisecondsSinceEpoch(v);
+    DateTime nowTime = DateTime(1, 1, 1, vTime.hour, vTime.minute, vTime.second);
+    return a <= nowTime.millisecondsSinceEpoch &&
+        nowTime.millisecondsSinceEpoch <= b;
+  }
+}

+ 116 - 0
lib/database/entity/BasicInfo.dart

@@ -0,0 +1,116 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+import 'package:flutter_habit/common/SqfliteDataBase.dart';
+
+class BasicInfo {
+  String tableName = "basicInfo";
+  Map<String, dynamic> value = {
+    "id" : null,
+    "height" : null,
+    "weight" : null,
+    "breastLine" : null,
+    "waistLine" : null,
+    "hipLine" : null,
+    "date" : null,
+  };
+  static Future<void> create() async {
+    Database database = SqfliteDataBase.getInstance();
+    await database.execute("""
+      CREATE TABLE IF NOT EXISTS basicInfo (  
+        id INTEGER PRIMARY KEY AUTOINCREMENT ,
+        height REAL NOT NULL ,
+        weight REAL NOT NULL ,
+        breastLine REAL NOT NULL ,
+        waistLine REAL NOT NULL ,
+        hipLine REAL NOT NULL ,
+        date INTEGER NOT NULL 
+      );
+    """);
+    debugPrint("create basicInfo");
+  }
+  
+  static Future<void> recreate() async {
+    Database database = SqfliteDataBase.getInstance();
+    await database.execute("""
+    DROP TABLE IF EXISTS basicInfo;
+    """);
+    debugPrint("drop basicInfo");
+    await create();
+  }
+
+  List<BasicInfo> resultAsList(List<Map<String, dynamic>> dbResult) {
+    return dbResult.map((value) {
+      BasicInfo entity = BasicInfo();
+      entity.value = value;
+      return entity;
+    }).toList();
+  }
+  
+  int getId() {
+    return value["id"];
+  }
+
+  BasicInfo setId(int id) {
+    value["id"] = id;
+    return this;
+  }
+  
+  double getHeight() {
+    return value["height"];
+  }
+
+  BasicInfo setHeight(double height) {
+    value["height"] = height;
+    return this;
+  }
+  
+  double getWeight() {
+    return value["weight"];
+  }
+
+  BasicInfo setWeight(double weight) {
+    value["weight"] = weight;
+    return this;
+  }
+  
+  double getBreastLine() {
+    return value["breastLine"];
+  }
+
+  BasicInfo setBreastLine(double cheatLine) {
+    value["breastLine"] = cheatLine;
+    return this;
+  }
+  
+  double getWaistLine() {
+    return value["waistLine"];
+  }
+
+  BasicInfo setWaistLine(double waistLine) {
+    value["waistLine"] = waistLine;
+    return this;
+  }
+  
+  double getHipLine() {
+    return value["hipLine"];
+  }
+
+  BasicInfo setHipLine(double hipLine) {
+    value["hipLine"] = hipLine;
+    return this;
+  }
+  
+  int getDate() {
+    return value["date"];
+  }
+
+  BasicInfo setDate(int date) {
+    value["date"] = date;
+    return this;
+  }
+  
+  @override
+  String toString() {
+    return value.toString();
+  }
+}

+ 83 - 0
lib/database/entity/ExerciseInfo.dart

@@ -0,0 +1,83 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+import 'package:flutter_habit/common/SqfliteDataBase.dart';
+
+class ExerciseInfo {
+  String tableName = "exerciseInfo";
+  Map<String, dynamic> value = {
+    "id" : null,
+    "sportId" : null,
+    "exerciseTime" : null,
+    "exerciseQuantity" : null,
+  };
+  static Future<void> create() async {
+    Database database = SqfliteDataBase.getInstance();
+    await database.execute("""
+      CREATE TABLE IF NOT EXISTS exerciseInfo (  
+        id INTEGER PRIMARY KEY AUTOINCREMENT ,
+        sportId INTEGER NOT NULL ,
+        exerciseTime INTEGER NOT NULL ,
+        exerciseQuantity REAL NOT NULL 
+      );
+    """);
+    debugPrint("create exerciseInfo");
+  }
+  
+  static Future<void> recreate() async {
+    Database database = SqfliteDataBase.getInstance();
+    await database.execute("""
+    DROP TABLE IF EXISTS exerciseInfo;
+    """);
+    debugPrint("drop exerciseInfo");
+    await create();
+  }
+
+  List<ExerciseInfo> resultAsList(List<Map<String, dynamic>> dbResult) {
+    return dbResult.map((value) {
+      ExerciseInfo entity = ExerciseInfo();
+      entity.value = value;
+      return entity;
+    }).toList();
+  }
+  
+  int getId() {
+    return value["id"];
+  }
+
+  ExerciseInfo setId(int id) {
+    value["id"] = id;
+    return this;
+  }
+  
+  int getSportId() {
+    return value["sportId"];
+  }
+
+  ExerciseInfo setSportId(int sportId) {
+    value["sportId"] = sportId;
+    return this;
+  }
+  
+  int getExerciseTime() {
+    return value["exerciseTime"];
+  }
+
+  ExerciseInfo setExerciseTime(int exerciseTime) {
+    value["exerciseTime"] = exerciseTime;
+    return this;
+  }
+  
+  double getExerciseQuantity() {
+    return value["exerciseQuantity"];
+  }
+
+  ExerciseInfo setExerciseQuantity(double exerciseQuantity) {
+    value["exerciseQuantity"] = exerciseQuantity;
+    return this;
+  }
+  
+  @override
+  String toString() {
+    return value.toString();
+  }
+}

+ 83 - 0
lib/database/entity/FoodInfo.dart

@@ -0,0 +1,83 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+import 'package:flutter_habit/common/SqfliteDataBase.dart';
+
+class FoodInfo {
+  String tableName = "foodInfo";
+  Map<String, dynamic> value = {
+    "id" : null,
+    "name" : null,
+    "gkCalorie" : null,
+    "eatTimes" : null,
+  };
+  static Future<void> create() async {
+    Database database = SqfliteDataBase.getInstance();
+    await database.execute("""
+      CREATE TABLE IF NOT EXISTS foodInfo (  
+        id INTEGER PRIMARY KEY AUTOINCREMENT ,
+        name TEXT UNIQUE NOT NULL ,
+        gkCalorie REAL NOT NULL ,
+        eatTimes INTEGER NOT NULL 
+      );
+    """);
+    debugPrint("create foodInfo");
+  }
+  
+  static Future<void> recreate() async {
+    Database database = SqfliteDataBase.getInstance();
+    await database.execute("""
+    DROP TABLE IF EXISTS foodInfo;
+    """);
+    debugPrint("drop foodInfo");
+    await create();
+  }
+
+  List<FoodInfo> resultAsList(List<Map<String, dynamic>> dbResult) {
+    return dbResult.map((value) {
+      FoodInfo entity = FoodInfo();
+      entity.value = value;
+      return entity;
+    }).toList();
+  }
+  
+  int getId() {
+    return value["id"];
+  }
+
+  FoodInfo setId(int id) {
+    value["id"] = id;
+    return this;
+  }
+  
+  String getName() {
+    return value["name"];
+  }
+
+  FoodInfo setName(String name) {
+    value["name"] = name;
+    return this;
+  }
+  
+  double getHgkCalorie() {
+    return value["gkCalorie"];
+  }
+
+  FoodInfo setHgkCalorie(double gkCalorie) {
+    value["gkCalorie"] = gkCalorie;
+    return this;
+  }
+  
+  int getEatTimes() {
+    return value["eatTimes"];
+  }
+
+  FoodInfo setEatTimes(int eatTimes) {
+    value["eatTimes"] = eatTimes;
+    return this;
+  }
+  
+  @override
+  String toString() {
+    return value.toString();
+  }
+}

+ 226 - 0
lib/database/entity/LifeInfo.dart

@@ -0,0 +1,226 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+import 'package:flutter_habit/common/SqfliteDataBase.dart';
+
+class LifeInfo {
+  String tableName = "lifeInfo";
+  Map<String, dynamic> value = {
+    "id" : null,
+    "date" : null,
+    "getUpTime" : null,
+    "breakfastTime" : null,
+    "breakfastId" : null,
+    "breakfastQuantity" : null,
+    "breakfastMoney" : null,
+    "midRestTime" : null,
+    "lunchTime" : null,
+    "lunchId" : null,
+    "lunchQuantity" : null,
+    "lunchMoney" : null,
+    "dinnerTime" : null,
+    "dinnerId" : null,
+    "dinnerQuantity" : null,
+    "dinnerMoney" : null,
+    "restTime" : null,
+  };
+  static Future<void> create() async {
+    Database database = SqfliteDataBase.getInstance();
+    await database.execute("""
+      CREATE TABLE IF NOT EXISTS lifeInfo (  
+        id INTEGER PRIMARY KEY AUTOINCREMENT ,
+        date INTEGER ,
+        getUpTime INTEGER ,
+        breakfastTime INTEGER ,
+        breakfastId INTEGER ,
+        breakfastQuantity REAL ,
+        breakfastMoney REAL ,
+        midRestTime INTEGER ,
+        lunchTime INTEGER ,
+        lunchId INTEGER ,
+        lunchQuantity REAL ,
+        lunchMoney REAL ,
+        dinnerTime INTEGER ,
+        dinnerId INTEGER ,
+        dinnerQuantity REAL ,
+        dinnerMoney REAL ,
+        restTime INTEGER 
+      );
+    """);
+    debugPrint("create lifeInfo");
+  }
+  
+  static Future<void> recreate() async {
+    Database database = SqfliteDataBase.getInstance();
+    await database.execute("""
+    DROP TABLE IF EXISTS lifeInfo;
+    """);
+    debugPrint("drop lifeInfo");
+    await create();
+  }
+
+  List<LifeInfo> resultAsList(List<Map<String, dynamic>> dbResult) {
+    return dbResult.map((value) {
+      LifeInfo entity = LifeInfo();
+      entity.value = value;
+      return entity;
+    }).toList();
+  }
+  
+  int getId() {
+    return value["id"];
+  }
+
+  LifeInfo setId(int id) {
+    value["id"] = id;
+    return this;
+  }
+  
+  int getDate() {
+    return value["date"];
+  }
+
+  LifeInfo setDate(int date) {
+    value["date"] = date;
+    return this;
+  }
+  
+  int getGetUpTime() {
+    return value["getUpTime"];
+  }
+
+  LifeInfo setGetUpTime(int getUpTime) {
+    value["getUpTime"] = getUpTime;
+    return this;
+  }
+  
+  int getBreakfastTime() {
+    return value["breakfastTime"];
+  }
+
+  LifeInfo setBreakfastTime(int breakfastTime) {
+    value["breakfastTime"] = breakfastTime;
+    return this;
+  }
+  
+  int getBreakfastId() {
+    return value["breakfastId"];
+  }
+
+  LifeInfo setBreakfastId(int breakfastId) {
+    value["breakfastId"] = breakfastId;
+    return this;
+  }
+  
+  double getBreakfastQuantity() {
+    return value["breakfastQuantity"];
+  }
+
+  LifeInfo setBreakfastQuantity(double breakfastQuantity) {
+    value["breakfastQuantity"] = breakfastQuantity;
+    return this;
+  }
+  
+  double getBreakfastMoney() {
+    return value["breakfastMoney"];
+  }
+
+  LifeInfo setBreakfastMoney(double breakfastMoney) {
+    value["breakfastMoney"] = breakfastMoney;
+    return this;
+  }
+  
+  int getMidRestTime() {
+    return value["midRestTime"];
+  }
+
+  LifeInfo setMidRestTime(int midRestTime) {
+    value["midRestTime"] = midRestTime;
+    return this;
+  }
+  
+  int getLunchTime() {
+    return value["lunchTime"];
+  }
+
+  LifeInfo setLunchTime(int lunchTime) {
+    value["lunchTime"] = lunchTime;
+    return this;
+  }
+  
+  int getLunchId() {
+    return value["lunchId"];
+  }
+
+  LifeInfo setLunchId(int lunchId) {
+    value["lunchId"] = lunchId;
+    return this;
+  }
+  
+  double getLunchQuantity() {
+    return value["lunchQuantity"];
+  }
+
+  LifeInfo setLunchQuantity(double lunchQuantity) {
+    value["lunchQuantity"] = lunchQuantity;
+    return this;
+  }
+  
+  double getLunchMoney() {
+    return value["lunchMoney"];
+  }
+
+  LifeInfo setLunchMoney(double lunchMoney) {
+    value["lunchMoney"] = lunchMoney;
+    return this;
+  }
+  
+  int getDinnerTime() {
+    return value["dinnerTime"];
+  }
+
+  LifeInfo setDinnerTime(int dinnerTime) {
+    value["dinnerTime"] = dinnerTime;
+    return this;
+  }
+  
+  int getDinnerId() {
+    return value["dinnerId"];
+  }
+
+  LifeInfo setDinnerId(int dinnerId) {
+    value["dinnerId"] = dinnerId;
+    return this;
+  }
+  
+  double getDinnerQuantity() {
+    return value["dinnerQuantity"];
+  }
+
+  LifeInfo setDinnerQuantity(double dinnerQuantity) {
+    value["dinnerQuantity"] = dinnerQuantity;
+    return this;
+  }
+  
+  double getDinnerMoney() {
+    return value["dinnerMoney"];
+  }
+
+  LifeInfo setDinnerMoney(double dinnerMoney) {
+    value["dinnerMoney"] = dinnerMoney;
+    return this;
+  }
+  
+  int getRestTime() {
+    return value["restTime"];
+  }
+
+  LifeInfo setRestTime(int restTime) {
+    value["restTime"] = restTime;
+    return this;
+  }
+  
+  @override
+  String toString() {
+    return value.toString();
+  }
+}

+ 72 - 0
lib/database/entity/ScheduledExercise.dart

@@ -0,0 +1,72 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+import 'package:flutter_habit/common/SqfliteDataBase.dart';
+
+class ScheduledExercise {
+  String tableName = "scheduledExercise";
+  Map<String, dynamic> value = {
+    "id" : null,
+    "sportId" : null,
+    "quantity" : null,
+  };
+  static Future<void> create() async {
+    Database database = SqfliteDataBase.getInstance();
+    await database.execute("""
+      CREATE TABLE IF NOT EXISTS scheduledExercise (  
+        id INTEGER PRIMARY KEY AUTOINCREMENT ,
+        sportId INTEGER ,
+        quantity REAL 
+      );
+    """);
+    debugPrint("create scheduledExercise");
+  }
+  
+  static Future<void> recreate() async {
+    Database database = SqfliteDataBase.getInstance();
+    await database.execute("""
+    DROP TABLE IF EXISTS scheduledExercise;
+    """);
+    debugPrint("drop scheduledExercise");
+    await create();
+  }
+
+  List<ScheduledExercise> resultAsList(List<Map<String, dynamic>> dbResult) {
+    return dbResult.map((value) {
+      ScheduledExercise entity = ScheduledExercise();
+      entity.value = value;
+      return entity;
+    }).toList();
+  }
+  
+  int getId() {
+    return value["id"];
+  }
+
+  ScheduledExercise setId(int id) {
+    value["id"] = id;
+    return this;
+  }
+  
+  int getSportId() {
+    return value["sportId"];
+  }
+
+  ScheduledExercise setSportId(int sportId) {
+    value["sportId"] = sportId;
+    return this;
+  }
+  
+  double getQuantity() {
+    return value["quantity"];
+  }
+
+  ScheduledExercise setQuantity(double quantity) {
+    value["quantity"] = quantity;
+    return this;
+  }
+  
+  @override
+  String toString() {
+    return value.toString();
+  }
+}

+ 83 - 0
lib/database/entity/SportInfo.dart

@@ -0,0 +1,83 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+import 'package:flutter_habit/common/SqfliteDataBase.dart';
+
+class SportInfo {
+  String tableName = "sportInfo";
+  Map<String, dynamic> value = {
+    "id" : null,
+    "name" : null,
+    "hkCalorie" : null,
+    "sportTimes" : null,
+  };
+  static Future<void> create() async {
+    Database database = SqfliteDataBase.getInstance();
+    await database.execute("""
+      CREATE TABLE IF NOT EXISTS sportInfo (  
+        id INTEGER PRIMARY KEY AUTOINCREMENT ,
+        name TEXT UNIQUE NOT NULL ,
+        hkCalorie REAL NOT NULL ,
+        sportTimes INTEGER NOT NULL 
+      );
+    """);
+    debugPrint("create sportInfo");
+  }
+  
+  static Future<void> recreate() async {
+    Database database = SqfliteDataBase.getInstance();
+    await database.execute("""
+    DROP TABLE IF EXISTS sportInfo;
+    """);
+    debugPrint("drop sportInfo");
+    await create();
+  }
+
+  List<SportInfo> resultAsList(List<Map<String, dynamic>> dbResult) {
+    return dbResult.map((value) {
+      SportInfo entity = SportInfo();
+      entity.value = value;
+      return entity;
+    }).toList();
+  }
+  
+  int getId() {
+    return value["id"];
+  }
+
+  SportInfo setId(int id) {
+    value["id"] = id;
+    return this;
+  }
+  
+  String getName() {
+    return value["name"];
+  }
+
+  SportInfo setName(String name) {
+    value["name"] = name;
+    return this;
+  }
+  
+  double getHkCalorie() {
+    return value["hkCalorie"];
+  }
+
+  SportInfo setHkCalorie(double hkCalorie) {
+    value["hkCalorie"] = hkCalorie;
+    return this;
+  }
+  
+  int getSportTimes() {
+    return value["sportTimes"];
+  }
+
+  SportInfo setSportTimes(int sportTimes) {
+    value["sportTimes"] = sportTimes;
+    return this;
+  }
+  
+  @override
+  String toString() {
+    return value.toString();
+  }
+}

+ 149 - 0
lib/database/entity/StudyInfo.dart

@@ -0,0 +1,149 @@
+import 'package:flutter/material.dart';
+import 'package:sqflite/sqflite.dart';
+import 'package:flutter_habit/common/SqfliteDataBase.dart';
+
+class StudyInfo {
+  String tableName = "studyInfo";
+  Map<String, dynamic> value = {
+    "id" : null,
+    "date" : null,
+    "courseName" : null,
+    "isLate" : null,
+    "isAbsent" : null,
+    "isHomeWorkDone" : null,
+    "homeworks" : null,
+    "difficulty" : null,
+    "isTroublesSolved" : null,
+    "troubles" : null,
+  };
+  static Future<void> create() async {
+    Database database = SqfliteDataBase.getInstance();
+    await database.execute("""
+      CREATE TABLE IF NOT EXISTS studyInfo (  
+        id INTEGER PRIMARY KEY AUTOINCREMENT ,
+        date INTEGER ,
+        courseName TEXT ,
+        isLate INTEGER ,
+        isAbsent INTEGER ,
+        isHomeWorkDone INTEGER ,
+        homeworks TEXT ,
+        difficulty INTEGER ,
+        isTroublesSolved INTEGER ,
+        troubles TEXT 
+      );
+    """);
+    debugPrint("create studyInfo");
+  }
+  
+  static Future<void> recreate() async {
+    Database database = SqfliteDataBase.getInstance();
+    await database.execute("""
+    DROP TABLE IF EXISTS studyInfo;
+    """);
+    debugPrint("drop studyInfo");
+    await create();
+  }
+
+  List<StudyInfo> resultAsList(List<Map<String, dynamic>> dbResult) {
+    return dbResult.map((value) {
+      StudyInfo entity = StudyInfo();
+      entity.value = value;
+      return entity;
+    }).toList();
+  }
+  
+  int getId() {
+    return value["id"];
+  }
+
+  StudyInfo setId(int id) {
+    value["id"] = id;
+    return this;
+  }
+  
+  int getDate() {
+    return value["date"];
+  }
+
+  StudyInfo setDate(int date) {
+    value["date"] = date;
+    return this;
+  }
+  
+  String getCourseName() {
+    return value["courseName"];
+  }
+
+  StudyInfo setCourseName(String courseName) {
+    value["courseName"] = courseName;
+    return this;
+  }
+  
+  int getIsLate() {
+    return value["isLate"];
+  }
+
+  StudyInfo setIsLate(int isLate) {
+    value["isLate"] = isLate;
+    return this;
+  }
+  
+  int getIsAbsent() {
+    return value["isAbsent"];
+  }
+
+  StudyInfo setIsAbsent(int isAbsent) {
+    value["isAbsent"] = isAbsent;
+    return this;
+  }
+  
+  int getIsHomeWorkDone() {
+    return value["isHomeWorkDone"];
+  }
+
+  StudyInfo setIsHomeWorkDone(int isHomeWorkDone) {
+    value["isHomeWorkDone"] = isHomeWorkDone;
+    return this;
+  }
+  
+  String getHomeworks() {
+    return value["homeworks"];
+  }
+
+  StudyInfo setHomeworks(String homeworks) {
+    value["homeworks"] = homeworks;
+    return this;
+  }
+  
+  int getDifficulty() {
+    return value["difficulty"];
+  }
+
+  StudyInfo setDifficulty(int difficulty) {
+    value["difficulty"] = difficulty;
+    return this;
+  }
+  
+  int getIsTroublesSolved() {
+    return value["isTroublesSolved"];
+  }
+
+  StudyInfo setIsTroublesSolved(int isTroublesSolved) {
+    value["isTroublesSolved"] = isTroublesSolved;
+    return this;
+  }
+  
+  String getTroubles() {
+    return value["troubles"];
+  }
+
+  StudyInfo setTroubles(String troubles) {
+    value["troubles"] = troubles;
+    return this;
+  }
+  
+  @override
+  String toString() {
+    return value.toString();
+  }
+}

+ 11 - 0
lib/database/mapper/BasicInfoMapper.dart

@@ -0,0 +1,11 @@
+import 'package:flutter_habit/common/BaseArchitectural.dart';
+import 'package:flutter_habit/database/entity/BasicInfo.dart';
+
+class BasicInfoMapper extends CommonMapper<BasicInfo> {
+  BasicInfoMapper._() : super(BasicInfo());
+  static BasicInfoMapper _instance = BasicInfoMapper._();
+
+  factory BasicInfoMapper() {
+    return _instance;
+  }
+}

+ 11 - 0
lib/database/mapper/ExerciseInfoMapper.dart

@@ -0,0 +1,11 @@
+import 'package:flutter_habit/common/BaseArchitectural.dart';
+import 'package:flutter_habit/database/entity/ExerciseInfo.dart';
+
+class ExerciseInfoMapper extends CommonMapper<ExerciseInfo> {
+  ExerciseInfoMapper._() : super(ExerciseInfo());
+  static ExerciseInfoMapper _instance = ExerciseInfoMapper._();
+
+  factory ExerciseInfoMapper() {
+    return _instance;
+  }
+}

+ 11 - 0
lib/database/mapper/FoodInfoMapper.dart

@@ -0,0 +1,11 @@
+import 'package:flutter_habit/common/BaseArchitectural.dart';
+import 'package:flutter_habit/database/entity/FoodInfo.dart';
+
+class FoodInfoMapper extends CommonMapper<FoodInfo> {
+  FoodInfoMapper._() : super(FoodInfo());
+  static FoodInfoMapper _instance = FoodInfoMapper._();
+
+  factory FoodInfoMapper() {
+    return _instance;
+  }
+}

+ 11 - 0
lib/database/mapper/LifeInfoMapper.dart

@@ -0,0 +1,11 @@
+import 'package:flutter_habit/common/BaseArchitectural.dart';
+import 'package:flutter_habit/database/entity/LifeInfo.dart';
+
+class LifeInfoMapper extends CommonMapper<LifeInfo> {
+  LifeInfoMapper._() : super(LifeInfo());
+  static LifeInfoMapper _instance = LifeInfoMapper._();
+
+  factory LifeInfoMapper() {
+    return _instance;
+  }
+}

+ 11 - 0
lib/database/mapper/ScheduledExerciseMapper.dart

@@ -0,0 +1,11 @@
+import 'package:flutter_habit/common/BaseArchitectural.dart';
+import 'package:flutter_habit/database/entity/ScheduledExercise.dart';
+
+class ScheduledExerciseMapper extends CommonMapper<ScheduledExercise> {
+  ScheduledExerciseMapper._() : super(ScheduledExercise());
+  static ScheduledExerciseMapper _instance = ScheduledExerciseMapper._();
+
+  factory ScheduledExerciseMapper() {
+    return _instance;
+  }
+}

+ 12 - 0
lib/database/mapper/SportInfoMapper.dart

@@ -0,0 +1,12 @@
+
+import 'package:flutter_habit/common/BaseArchitectural.dart';
+import 'package:flutter_habit/database/entity/SportInfo.dart';
+
+class SportInfoMapper extends CommonMapper<SportInfo> {
+  SportInfoMapper._() : super(SportInfo());
+  static SportInfoMapper _instance = SportInfoMapper._();
+
+  factory SportInfoMapper() {
+    return _instance;
+  }
+}

+ 11 - 0
lib/database/mapper/StudyInfoMapper.dart

@@ -0,0 +1,11 @@
+import 'package:flutter_habit/common/BaseArchitectural.dart';
+import 'package:flutter_habit/database/entity/StudyInfo.dart';
+
+class StudyInfoMapper extends CommonMapper<StudyInfo> {
+  StudyInfoMapper._() : super(StudyInfo());
+  static StudyInfoMapper _instance = StudyInfoMapper._();
+
+  factory StudyInfoMapper() {
+    return _instance;
+  }
+}

+ 49 - 0
lib/main.dart

@@ -0,0 +1,49 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_habit/common/provider/ConfigProvider.dart';
+import 'package:flutter_habit/common/provider/DataProvider.dart';
+import 'package:flutter_habit/common/provider/UserProvider.dart';
+import 'package:flutter_habit/view/LoadingPage.dart';
+import 'package:provider/provider.dart';
+
+import 'common/provider/NotificationProvider.dart';
+import 'common/provider/ThemeProvider.dart';
+
+void main() {
+  runApp(
+    MultiProvider(
+      providers: [
+        ChangeNotifierProvider<ThemeProvider>(
+          create: (_) => ThemeProvider(),
+        ),
+        ChangeNotifierProvider<UserProvider>(
+          create: (_) => UserProvider(),
+        ),
+        ChangeNotifierProvider<DataProvider>(
+          create: (_) => DataProvider(),
+        ),
+        ChangeNotifierProvider<ConfigProvider>(
+          create: (_) => ConfigProvider(),
+        ),
+        ChangeNotifierProvider<NotificationProvider>(
+          create: (_) => NotificationProvider(),
+        ),
+      ],
+      child: MyApp(),
+    ),
+  );
+}
+
+class MyApp extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      title: 'Flutter Demo',
+      theme: ThemeData(
+        primarySwatch: Provider.of<ThemeProvider>(context).currentMaterialColor,
+        brightness: Provider.of<ThemeProvider>(context).currentBrightness,
+      ),
+//      home: LoadingPage(),
+      home: LoadingPage(),
+    );
+  }
+}

+ 20 - 0
lib/network/Api.dart

@@ -0,0 +1,20 @@
+class Api {
+  static const String baseUrl = "http://106.15.249.67:8910/api";
+//  static const String baseUrl = "http://10.0.2.2/api";
+
+  static const String token = "$baseUrl/permission/token";
+
+  static const String authCode = "$baseUrl/permission/authCode";
+
+  static const String user = "$baseUrl/user";
+
+  static const String community = "$baseUrl/community";
+
+  static const String shopping = "$baseUrl/shopping";
+
+  static const String follow = "$community/follow";
+
+
+  static const String coinTop = "$user/coinTop";
+
+}

+ 633 - 0
lib/network/Repository.dart

@@ -0,0 +1,633 @@
+import 'package:dio/dio.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_habit/common/I18N.dart';
+import 'package:flutter_habit/common/components/PopMenus.dart';
+import 'package:flutter_habit/common/provider/UserProvider.dart';
+import 'package:flutter_habit/common/utils/ConvertUtils.dart';
+import 'package:provider/provider.dart';
+
+import 'Api.dart';
+import 'Status.dart';
+
+class Repository {
+  Dio _dio;
+
+  Repository._() {
+    if (_dio == null) {
+      _dio = Dio(
+        BaseOptions(
+          connectTimeout: Duration(milliseconds: 8000),
+          receiveTimeout: Duration(milliseconds: 8000),
+        ),
+      );
+    }
+  }
+
+  static Repository _repository;
+
+  static Repository getInstance() {
+    if (_repository == null) {
+      _repository = Repository._();
+    }
+    return _repository;
+  }
+
+  Future<bool> sendAuthCode(
+    BuildContext context,
+    String email,
+    String purpose,
+  ) async {
+    Response response;
+    try {
+      response = await _dio.post(
+        Api.authCode,
+        data: {
+          "email": email,
+          "purpose": purpose,
+        },
+      );
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("验证码发送成功,5分钟内有效")));
+        return true;
+
+      case Status.RES_REPEATED:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("该邮箱已存在")));
+        break;
+
+      case Status.RES_NOT_FOUND:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("该邮箱未注册")));
+        break;
+
+      case Status.CREATE_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("邮件发送失败")));
+        break;
+    }
+    return false;
+  }
+
+  Future<bool> signUp(
+    BuildContext context,
+    String authCode,
+    String email,
+    String pwd,
+  ) async {
+    Response response;
+    try {
+      response = await _dio.post(
+        "${Api.user}/",
+        options: Options(
+          headers: {
+            "authCode": authCode,
+          },
+        ),
+        data: {
+          "email": email,
+          "pwd": ConvertUtils.md5Encode(pwd),
+        },
+      );
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("注册成功")));
+        return true;
+
+      case Status.RES_NOT_MATCH:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("验证码错误或过期")));
+        break;
+
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return false;
+  }
+
+  Future<bool> resetPwd(
+    BuildContext context,
+    String authCode,
+    String email,
+    String pwd,
+  ) async {
+    Response response;
+    try {
+      response = await _dio.put(
+        "${Api.user}/",
+        options: Options(
+          headers: {
+            "authCode": authCode,
+          },
+        ),
+        data: {
+          "email": email,
+          "pwd": ConvertUtils.md5Encode(pwd),
+        },
+      );
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("修改密码成功")));
+        return true;
+
+      case Status.RES_NOT_MATCH:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("验证码错误或过期")));
+        break;
+
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return false;
+  }
+
+  Future<Map> signIn(
+    BuildContext context,
+    String email,
+    String pwd,
+  ) async {
+    Response response;
+    try {
+      response = await _dio.post(
+        Api.token,
+        data: {
+          "email": email,
+          "pwd": ConvertUtils.md5Encode(pwd),
+        },
+      );
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        return response.data;
+
+      case Status.RES_NOT_MATCH:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("邮箱或密码错误")));
+        break;
+
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return {};
+  }
+
+  Future<Map> getUserInfo(int uid) async {
+    Response response;
+    try {
+      response = await _dio.get("${Api.user}/$uid/userInfo");
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    if (Status.of(response) == Status.OK) {
+      return response.data;
+    }
+    return {};
+  }
+
+  Future<int> getCoin(int uid) async {
+    Response response;
+    try {
+      response = await _dio.get("${Api.user}/$uid/coin");
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    if (Status.of(response) == Status.OK) {
+      return response.data;
+    }
+    return null;
+  }
+
+  Future<List<int>> getPhoto(int uid) async {
+    Response response;
+    try {
+      response = await _dio.get(
+        "${Api.user}/$uid/userPhoto",
+        options: Options(
+          responseType: ResponseType.bytes,
+        ),
+      );
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    if (Status.of(response) == Status.OK) {
+      return response.data;
+    }
+    return null;
+  }
+
+  Future<bool> uploadPhoto(
+      BuildContext context, String token, int uid, List<int> photo) async {
+    Response response;
+    try {
+      response = await _dio.put("${Api.user}/$uid/userPhoto",
+          options: Options(
+            headers: {
+              "token": token,
+            },
+          ),
+          data: {
+            "photo": photo,
+          });
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        return true;
+
+      case Status.INVALID_AUTHORIZE:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("登录信息过期")));
+        await Provider.of<UserProvider>(context, listen: false)
+            .cleanDataAndBackToHome(context);
+        break;
+
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return false;
+  }
+
+  Future<bool> modifyUserInfo(
+    BuildContext context,
+    String token,
+    int uid,
+    String userName,
+    String gender,
+    String birthday,
+  ) async {
+    Response response;
+    try {
+      response = await _dio.put(
+        "${Api.user}/$uid/userInfo",
+        options: Options(
+          headers: {
+            "token": token,
+          },
+        ),
+        data: {
+          "userName": userName,
+          "gender": gender,
+          "birthday": birthday,
+        },
+      );
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        return true;
+
+      case Status.INVALID_AUTHORIZE:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("登录信息过期")));
+        await Provider.of<UserProvider>(context, listen: false)
+            .cleanDataAndBackToHome(context);
+        break;
+
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return false;
+  }
+
+  Future<bool> getFollowState(
+      BuildContext context, String token, int uid, int followUid) async {
+    Response response;
+    try {
+      response = await _dio.get("${Api.community}/$uid/follow/$followUid",
+          options: Options(
+            headers: {
+              "token": token,
+            },
+          ));
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        return response.data;
+      case Status.INVALID_AUTHORIZE:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("登录信息过期")));
+        await Provider.of<UserProvider>(context, listen: false)
+            .cleanDataAndBackToHome(context);
+        break;
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return false;
+  }
+
+  Future<String> follow(
+      BuildContext context, String token, int uid, int followUid) async {
+    Response response;
+    try {
+      response = await _dio.post(
+        Api.follow,
+        options: Options(
+          headers: {
+            "token": token,
+          },
+        ),
+        data: {
+          "uid": uid,
+          "followUid": followUid,
+        },
+      );
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        return response.data;
+      case Status.RES_NOT_ALLOW:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("不能关注自己")));
+        break;
+      case Status.INVALID_AUTHORIZE:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("登录信息过期")));
+        await Provider.of<UserProvider>(context, listen: false)
+            .cleanDataAndBackToHome(context);
+        break;
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return null;
+  }
+
+  Future<List> getFollowList(
+      BuildContext context, String token, int uid) async {
+    Response response;
+    try {
+      response = await _dio.get(
+        Api.follow,
+        options: Options(
+          headers: {
+            "token": token,
+          },
+        ),
+        queryParameters: {
+          "uid": uid,
+        },
+      );
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        return response.data;
+
+      case Status.INVALID_AUTHORIZE:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("登录信息过期")));
+        await Provider.of<UserProvider>(context, listen: false)
+            .cleanDataAndBackToHome(context);
+        break;
+
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return [];
+  }
+
+  Future<List> getUserInfoLikeUserName(
+      BuildContext context, String name) async {
+    Response response;
+    try {
+      response = await _dio.get("${Api.user}/", queryParameters: {
+        "name": name,
+      });
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        return response.data;
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return [];
+  }
+
+  Future<List> getCoinTop(BuildContext context, int topCount) async {
+    Response response;
+    try {
+      response = await _dio.get("${Api.community}/coinTop", queryParameters: {
+        "topCount": topCount,
+      });
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        return response.data;
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return [];
+  }
+
+  Future<List> getGoodsList(BuildContext context) async {
+    Response response;
+    try {
+      response = await _dio.get(
+        "${Api.shopping}/",
+      );
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        return response.data;
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return [];
+  }
+
+  Future<int> increaseCoin(BuildContext context, int uid, String token) async {
+    Response response;
+    try {
+      response = await _dio.put(
+        "${Api.user}/$uid/coin",
+        options: Options(headers: {
+          "token": token,
+        }),
+      );
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        return response.data;
+      case Status.INVALID_AUTHORIZE:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("登录信息过期")));
+        await Provider.of<UserProvider>(context, listen: false)
+            .cleanDataAndBackToHome(context);
+        break;
+      case Status.RES_NOT_ALLOW:
+        await PopMenus.ban(context);
+        Provider.of<UserProvider>(context, listen: false).coins = -666;
+        Provider.of<UserProvider>(context, listen: false).refresh();
+        break;
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return null;
+  }
+
+  Future<bool> buyGoods(
+      BuildContext context, String token, int uid, int goodsId) async {
+    Response response;
+    try {
+      response = await _dio.post("${Api.shopping}/",
+          options: Options(headers: {
+            "token": token,
+          }),
+          data: {
+            "uid": uid,
+            "goodsId": goodsId,
+          });
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        return true;
+      case Status.INVALID_AUTHORIZE:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("登录信息过期")));
+        await Provider.of<UserProvider>(context, listen: false)
+            .cleanDataAndBackToHome(context);
+        break;
+      case Status.RES_NOT_ALLOW:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("金币不足")));
+        break;
+      case Status.RES_NOT_MATCH:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("库存不足")));
+        break;
+      case Status.CREATE_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("邮件发送失败")));
+        break;
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return false;
+  }
+
+  Future<bool> uploadDB(
+      BuildContext context, int uid, String token, List<int> data) async {
+    Response response;
+    try {
+      response = await _dio.put(
+        "${Api.user}/$uid/data",
+        options: Options(headers: {
+          "token": token,
+        }),
+        data: {"data": data},
+      );
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        return true;
+      case Status.INVALID_AUTHORIZE:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("登录信息过期")));
+        await Provider.of<UserProvider>(context, listen: false)
+            .cleanDataAndBackToHome(context);
+        break;
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return false;
+  }
+
+  Future<List<int>> downloadDB(
+      BuildContext context, int uid, String token) async {
+    Response response;
+    try {
+      response = await _dio.get(
+        "${Api.user}/$uid/data",
+        options: Options(
+          responseType: ResponseType.bytes,
+          headers: {
+            "token": token,
+          },
+        ),
+      );
+    } catch (e) {
+      debugPrint(e.toString());
+    }
+    switch (Status.of(response)) {
+      case Status.OK:
+        return response.data;
+      case Status.RES_NOT_FOUND:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("云端无数据")));
+        await Provider.of<UserProvider>(context, listen: false)
+            .cleanDataAndBackToHome(context);
+        break;
+      case Status.INVALID_AUTHORIZE:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("登录信息过期")));
+        await Provider.of<UserProvider>(context, listen: false)
+            .cleanDataAndBackToHome(context);
+        break;
+      case Status.CONNECT_FAIL:
+        await PopMenus.attention(
+            context: context, content: Text(I18N.of("连接失败")));
+        break;
+    }
+    return null;
+  }
+}

+ 21 - 0
lib/network/Status.dart

@@ -0,0 +1,21 @@
+import 'package:dio/dio.dart';
+
+class Status {
+  static const String OK = "OK";
+  static const String RES_REPEATED = "RES_REPEATED";
+  static const String RES_NOT_MATCH = "RES_NOT_MATCH";
+  static const String INVALID_AUTHORIZE = "INVALID_AUTHORIZE";
+  static const String RES_NOT_FOUND = "RES_NOT_FOUND";
+  static const String CREATE_FAIL = "CREATE_FAIL";
+  static const String CONNECT_FAIL = "CONNECT_FAIL";
+  static const String RES_NOT_ALLOW = "RES_NOT_ALLOW";
+
+  static String of(Response response) {
+    if (response == null) {
+      return CONNECT_FAIL;
+    }
+    else {
+      return response.headers.value("responseCode");
+    }
+  }
+}

+ 167 - 0
lib/view/HomePage.dart

@@ -0,0 +1,167 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_habit/common/BaseArchitectural.dart';
+import 'package:flutter_habit/common/I18N.dart';
+import 'package:flutter_habit/common/provider/UserProvider.dart';
+import 'package:flutter_habit/view/context/ExerciseInfoContext.dart';
+import 'package:flutter_habit/view/context/LifeInfoContext.dart';
+import 'package:flutter_habit/view/context/BasicInfoContext.dart';
+import 'package:flutter_habit/view/context/StudyInfoContext.dart';
+import 'package:flutter_habit/view/drawer/HomePageDrawer.dart';
+import 'package:flutter_habit/view/record/BasicInfoRecordingPage.dart';
+import 'package:flutter_habit/view/record/ExerciseInfoRecordingPage.dart';
+import 'package:flutter_habit/view/record/LifeInfoRecordingPage.dart';
+import 'package:flutter_habit/view/record/StudyInfoRecordingPage.dart';
+import 'package:provider/provider.dart';
+
+class HomePage extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return MultiProvider(
+      providers: [
+        ChangeNotifierProvider<HomePageService>(
+            create: (_) => HomePageService(context)),
+        ChangeNotifierProvider<HomePageModel>(
+            create: (_) => HomePageModel(context)),
+      ],
+      child: _HomePageView(),
+    );
+  }
+}
+
+// model
+class HomePageModel extends BaseModel {
+  HomePageModel(BuildContext context) : super(context);
+
+  int currentIndex;
+
+  PageController pageViewController;
+
+  @override
+  void init(BuildContext context) {
+    super.init(context);
+    currentIndex = 0;
+    pageViewController = PageController(initialPage: 0);
+  }
+}
+
+// service
+class HomePageService extends BaseService {
+  HomePageService(BuildContext context) : super(context);
+
+  void changeNavigation(BuildContext context, int index) {
+    HomePageModel model = Provider.of<HomePageModel>(context, listen: false);
+    model.currentIndex = index;
+    model.pageViewController.animateToPage(index,
+        duration: Duration(milliseconds: 300), curve: Curves.ease);
+    model.refresh();
+  }
+
+  void onPageChanged(BuildContext context, int index) {
+    HomePageModel model = Provider.of<HomePageModel>(context, listen: false);
+    model.currentIndex = index;
+    model.refresh();
+  }
+
+  Future<void> toBasicInfoRecordingPage(BuildContext context) async {
+    await Navigator.of(context)
+        .push(MaterialPageRoute(builder: (_) => BasicInfoRecordingPage()));
+  }
+
+  Future<void> toLifeInfoRecordingPage(BuildContext context) async {
+    await Navigator.of(context)
+        .push(MaterialPageRoute(builder: (_) => LifeInfoRecordingPage()));
+  }
+
+  Future<void> toExerciseInfoRecordingPage(BuildContext context) async {
+    await Navigator.of(context)
+        .push(MaterialPageRoute(builder: (_) => ExerciseInfoRecordingPage()));
+  }
+
+  Future<void> toStudyInfoRecordingPage(BuildContext context) async {
+    await Navigator.of(context)
+        .push(MaterialPageRoute(builder: (_) => StudyInfoRecordingPage()));
+  }
+}
+
+// view
+class _HomePageView extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    HomePageService service =
+        Provider.of<HomePageService>(context, listen: false);
+    HomePageModel model = Provider.of<HomePageModel>(context, listen: true);
+    UserProvider userProvider =
+        Provider.of<UserProvider>(context, listen: true);
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(I18N.of(<String>[
+          "基本信息",
+          "日常生活",
+          "体育锻炼",
+          "课程学习",
+        ][model.currentIndex])),
+        actions: userProvider.token == null
+            ? []
+            : <Widget>[
+                Row(
+                  children: <Widget>[
+                    Text(
+                      "${userProvider.coins} x ",
+                      style: TextStyle(fontSize: 16),
+                    ),
+                    Icon(Icons.monetization_on),
+                    Text("  "),
+                  ],
+                ),
+              ],
+      ),
+      drawer: HomePageDrawer(),
+      body: PageView(
+        controller: model.pageViewController,
+        onPageChanged: (index) => service.onPageChanged(context, index),
+        physics: NeverScrollableScrollPhysics(),
+        children: <Widget>[
+          BasicInfoContext(),
+          LifeInfoContext(),
+          ExerciseInfoContext(),
+          StudyInfoContext(),
+        ],
+      ),
+      floatingActionButton: <Widget>[
+        FloatingActionButton(
+          child: Icon(Icons.playlist_add),
+          onPressed: () => service.toBasicInfoRecordingPage(context),
+        ),
+        FloatingActionButton(
+          child: Icon(Icons.playlist_add),
+          onPressed: () => service.toLifeInfoRecordingPage(context),
+        ),
+        FloatingActionButton(
+          child: Icon(Icons.playlist_add),
+          onPressed: () => service.toExerciseInfoRecordingPage(context),
+        ),
+        FloatingActionButton(
+          child: Icon(Icons.playlist_add),
+          onPressed: () => service.toStudyInfoRecordingPage(context),
+        ),
+      ][model.currentIndex],
+      bottomNavigationBar: BottomNavigationBar(
+        currentIndex: model.currentIndex,
+        onTap: (index) => service.changeNavigation(context, index),
+        showUnselectedLabels: true,
+        fixedColor: Theme.of(context).colorScheme.secondary,
+        unselectedItemColor: Theme.of(context).unselectedWidgetColor,
+        items: <BottomNavigationBarItem>[
+          BottomNavigationBarItem(
+              icon: Icon(Icons.accessibility), label: I18N.of("基本信息")),
+          BottomNavigationBarItem(
+              icon: Icon(Icons.wb_sunny), label: I18N.of("日常生活")),
+          BottomNavigationBarItem(
+              icon: Icon(Icons.directions_bike), label: I18N.of("体育锻炼")),
+          BottomNavigationBarItem(
+              icon: Icon(Icons.school), label: I18N.of("课程学习")),
+        ],
+      ),
+    );
+  }
+}

+ 107 - 0
lib/view/LoadingPage.dart

@@ -0,0 +1,107 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_habit/common/BaseArchitectural.dart';
+import 'package:flutter_habit/common/I18N.dart';
+import 'package:flutter_habit/common/LocalData.dart';
+import 'package:flutter_habit/common/SqfliteDataBase.dart';
+import 'package:flutter_habit/common/provider/ConfigProvider.dart';
+import 'package:flutter_habit/common/provider/DataProvider.dart';
+import 'package:flutter_habit/common/provider/NotificationProvider.dart';
+import 'package:flutter_habit/common/provider/UserProvider.dart';
+import 'package:flutter_habit/common/provider/ThemeProvider.dart';
+import 'package:flutter_habit/view/HomePage.dart';
+import 'package:provider/provider.dart';
+
+class LoadingPage extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return MultiProvider(
+      providers: [
+        ChangeNotifierProvider<LoadingPageService>(
+            create: (_) => LoadingPageService(context)),
+      ],
+      child: _LoadingPageView(),
+    );
+  }
+}
+
+// service
+class LoadingPageService extends BaseService {
+  LoadingPageService(BuildContext context) : super(context);
+
+  Timer timer;
+
+  bool isLoaded;
+
+  Future<void> loadData(BuildContext context) async {
+    // ================================== init ==================================
+    await LocalData.init();
+
+//    LocalData.getInstance().clear();
+
+    I18N.init();
+    Provider.of<ThemeProvider>(context, listen: false).init();
+    Provider.of<UserProvider>(context, listen: false).init();
+    Provider.of<ConfigProvider>(context, listen: false).init();
+    await Provider.of<NotificationProvider>(context, listen: false).init();
+    await SqfliteDataBase.init();
+//    await SqfliteDataBase.resetTables();
+    await Provider.of<DataProvider>(context, listen: false).init();
+    isLoaded = true;
+    // ================================== over ==================================
+  }
+
+  @override
+  void init(BuildContext context) {
+    super.init(context);
+    isLoaded = false;
+    timer = null;
+    startCountDown(context);
+    loadData(context);
+  }
+
+  void startCountDown(BuildContext context) {
+    if (timer == null) {
+      int minDuration = 3;
+      timer = Timer.periodic(Duration(seconds: 1), (t) {
+        minDuration--;
+        if (isLoaded && minDuration <= 0) {
+          timer.cancel();
+          scalaJumpToHomePage(context);
+        }
+      });
+    }
+  }
+
+  void scalaJumpToHomePage(BuildContext context) {
+    Navigator.of(context).pushReplacement(PageRouteBuilder(
+        transitionDuration: Duration(milliseconds: 600),
+        pageBuilder: (context, animation, secondaryAnimation) =>
+//          FadeTransition(opacity: animation, child: HomePage())));
+            ScaleTransition(scale: animation, child: HomePage())));
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    if (timer != null) {
+      timer.cancel();
+    }
+  }
+}
+
+class _LoadingPageView extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    LoadingPageService service =
+        Provider.of<LoadingPageService>(context, listen: false);
+    return ConstrainedBox(
+      constraints: BoxConstraints.expand(),
+      child: Image.asset(
+        "res/welcome.png",
+        fit: BoxFit.fitWidth,
+      ),
+    );
+  }
+}

+ 195 - 0
lib/view/context/BasicInfoContext.dart

@@ -0,0 +1,195 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_habit/common/BaseArchitectural.dart';
+import 'package:flutter_habit/common/I18N.dart';
+import 'package:flutter_habit/common/provider/DataProvider.dart';
+import 'package:flutter_habit/common/provider/UserProvider.dart';
+import 'package:flutter_habit/common/utils/ConvertUtils.dart';
+import 'package:flutter_habit/view/context/widget/BaseCard.dart';
+import 'package:flutter_habit/view/context/widget/DateValueMultiLineChart.dart';
+import 'package:flutter_habit/view/context/widget/DateValueSingleLineChart.dart';
+import 'package:provider/provider.dart';
+
+class BasicInfoContext extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return MultiProvider(
+      providers: [
+        ChangeNotifierProvider<BasicInfoContextService>(
+            create: (_) => BasicInfoContextService(context)),
+      ],
+      child: _BasicInfoContextView(),
+    );
+  }
+}
+
+// service
+class BasicInfoContextService extends BaseService {
+  BasicInfoContextService(BuildContext context) : super(context);
+
+  void changeSizeOfWeightChartCard(BuildContext context) {
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: false);
+    switch (dataProvider.weightChartSize) {
+      case 7:
+        dataProvider.weightChartSize = 30;
+        break;
+      case 30:
+        dataProvider.weightChartSize = 90;
+        break;
+      case 90:
+        dataProvider.weightChartSize = 7;
+        break;
+    }
+    dataProvider.notifyListeners();
+  }
+
+  void changeSizeOfBwhChartCard(BuildContext context) {
+    DataProvider dataProvider =
+    Provider.of<DataProvider>(context, listen: false);
+    switch (dataProvider.bwhChartSize) {
+      case 7:
+        dataProvider.bwhChartSize = 30;
+        break;
+      case 30:
+        dataProvider.bwhChartSize = 90;
+        break;
+      case 90:
+        dataProvider.bwhChartSize = 7;
+        break;
+    }
+    dataProvider.notifyListeners();
+  }
+}
+
+// view
+class _BasicInfoContextView extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return ListView(
+      children: <Widget>[
+        baseInfoCard(context),
+        weightChartCard(context),
+        bwhChartCard(context),
+      ],
+    );
+  }
+
+  Widget baseInfoCard(BuildContext context) {
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: true);
+    UserProvider userProvider =
+        Provider.of<UserProvider>(context, listen: true);
+    return BaseCard(
+      title: Text(
+        userProvider.userName ?? "Habit",
+        maxLines: 1,
+        style: Theme.of(context).textTheme.titleLarge.copyWith(fontSize: 35),
+      ),
+      subtitle: Text(I18N.of("基本信息")),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: <Widget>[
+          userProvider.photo == null
+              ? Icon(
+                  Icons.account_box,
+                  size: 170,
+                  color: Theme.of(context).unselectedWidgetColor,
+                )
+              : Padding(
+                  padding: EdgeInsets.all(20),
+                  child: ClipRRect(
+                    borderRadius: BorderRadius.circular(10),
+                    child: Image.memory(
+                      userProvider.photo,
+                      width: 130,
+                      height: 130,
+                    ),
+                  ),
+                ),
+          Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: <Widget>[
+              Text(
+                "${I18N.of("身高")}: ${ConvertUtils.packString(dataProvider.height)} cm",
+                style: TextStyle(height: 1.5),
+              ),
+              Text(
+                "${I18N.of("体重")}: ${ConvertUtils.packString(dataProvider.weight)} kg",
+                style: TextStyle(height: 1.5),
+              ),
+              Text(
+                "${I18N.of("BMI")}:  ${ConvertUtils.packString(dataProvider.bmi)} kg/m²",
+                style: TextStyle(height: 1.5),
+              ),
+              Text(
+                "${I18N.of("胸围")}: ${ConvertUtils.packString(dataProvider.breastLine)} cm",
+                style: TextStyle(height: 1.5),
+              ),
+              Text(
+                "${I18N.of("腰围")}: ${ConvertUtils.packString(dataProvider.hipLine)} cm",
+                style: TextStyle(height: 1.5),
+              ),
+              Text(
+                "${I18N.of("臀围")}: ${ConvertUtils.packString(dataProvider.waistLine)} cm",
+                style: TextStyle(height: 1.5),
+              ),
+            ],
+          ),
+          Container(),
+        ],
+      ),
+    );
+  }
+
+  Widget weightChartCard(BuildContext context) {
+    BasicInfoContextService service =
+        Provider.of<BasicInfoContextService>(context, listen: false);
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: true);
+    return BaseCard(
+      title: Text(
+        "${I18N.of("体重信息")} (kg)",
+        style: Theme.of(context).textTheme.titleLarge,
+      ),
+      subtitle: Container(
+        height: 25,
+        child: TextButton(
+          child: Text("< ${dataProvider.weightChartSize} >"),
+          onPressed: () => service.changeSizeOfWeightChartCard(context),
+        ),
+      ),
+      child: DateValueSingleLineChart(
+        size: dataProvider.weightChartSize,
+        sports: dataProvider.weightFlSpots,
+      ),
+    );
+  }
+
+  Widget bwhChartCard(BuildContext context) {
+    BasicInfoContextService service =
+        Provider.of<BasicInfoContextService>(context, listen: false);
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: true);
+    return BaseCard(
+      title: Text(
+        "${I18N.of("三围信息")} (cm)",
+        style: Theme.of(context).textTheme.titleLarge,
+      ),
+      subtitle: Container(
+        height: 25,
+        child: TextButton(
+          child: Text("< ${dataProvider.bwhChartSize} >"),
+          onPressed: () => service.changeSizeOfBwhChartCard(context),
+        ),
+      ),
+      child: DateValueMultiLineChart(
+        size: dataProvider.bwhChartSize,
+        lineNameSportsMap: {
+          I18N.of("胸围") : dataProvider.brestLineFlSpots,
+          I18N.of("腰围") : dataProvider.waistLineFlSpots,
+          I18N.of("臀围") : dataProvider.hipLineFlSpots
+        },
+      ),
+    );
+  }
+}

+ 278 - 0
lib/view/context/ExerciseInfoContext.dart

@@ -0,0 +1,278 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_habit/common/BaseArchitectural.dart';
+import 'package:flutter_habit/common/I18N.dart';
+import 'package:flutter_habit/common/components/PopMenus.dart';
+import 'package:flutter_habit/common/provider/DataProvider.dart';
+import 'package:flutter_habit/common/provider/UserProvider.dart';
+import 'package:flutter_habit/common/utils/ConvertUtils.dart';
+import 'package:flutter_habit/database/entity/ExerciseInfo.dart';
+import 'package:flutter_habit/database/entity/ScheduledExercise.dart';
+import 'package:flutter_habit/database/entity/SportInfo.dart';
+import 'package:flutter_habit/database/mapper/ExerciseInfoMapper.dart';
+import 'package:flutter_habit/database/mapper/ScheduledExerciseMapper.dart';
+import 'package:flutter_habit/database/mapper/SportInfoMapper.dart';
+import 'package:flutter_habit/network/Repository.dart';
+import 'package:flutter_habit/view/context/widget/BaseCard.dart';
+import 'package:flutter_habit/view/context/widget/DateValueSingleLineChart.dart';
+import 'package:flutter_habit/view/record/sub/AddScheduledExercisePage.dart';
+import 'package:provider/provider.dart';
+
+class ExerciseInfoContext extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return MultiProvider(
+      providers: [
+        ChangeNotifierProvider<ExerciseInfoContextService>(
+            create: (_) => ExerciseInfoContextService(context)),
+      ],
+      child: _ExerciseInfoContextView(),
+    );
+  }
+}
+
+// service
+class ExerciseInfoContextService extends BaseService {
+  ExerciseInfoContextService(BuildContext context) : super(context);
+
+  Future<void> cancelScheduledExercise(BuildContext context,
+      MapEntry<ScheduledExercise, SportInfo> mapEntry) async {
+    await PopMenus.sliderConfirm(
+      context: context,
+      content: Text(I18N.of("滑动来删除该条数据")),
+      function: () async {
+        await ScheduledExerciseMapper().delete(mapEntry.key);
+        SportInfo sportInfo = SportInfo();
+        sportInfo.setId(mapEntry.value.getId());
+        sportInfo.setSportTimes(mapEntry.value.getSportTimes() - 1);
+        await SportInfoMapper().updateByFirstKeySelective(sportInfo);
+        await Provider.of<DataProvider>(context, listen: false)
+            .loadExerciseInfoData();
+      },
+    );
+  }
+
+  Future<void> completeScheduledExercise(BuildContext context,
+      MapEntry<ScheduledExercise, SportInfo> mapEntry) async {
+    await PopMenus.sliderConfirm(
+      context: context,
+      content: Text(I18N.of("滑动来完成计划")),
+      function: () async {
+        DateTime now = DateTime.now();
+        ExerciseInfo exerciseInfo = ExerciseInfo();
+        exerciseInfo.setSportId(mapEntry.key.getSportId());
+        exerciseInfo.setExerciseQuantity(mapEntry.key.getQuantity());
+        exerciseInfo.setExerciseTime(now.millisecondsSinceEpoch);
+        // 今日首次运动?
+        List<ExerciseInfo> localExerciseInfo = await ExerciseInfoMapper().selectWhere(
+            "exerciseTime > ${ConvertUtils.dateOfDateTime(now).millisecondsSinceEpoch}");
+        if (localExerciseInfo.isEmpty){
+          // 今日首次运动
+          // 增加金币
+          UserProvider userProvider =
+          Provider.of<UserProvider>(context, listen: false);
+          if (userProvider.token != null) {
+            int increasedCoin = await Repository.getInstance()
+                .increaseCoin(context, userProvider.uid, userProvider.token);
+            if (increasedCoin != null) {
+              await PopMenus.coinAdd(
+                  context: context, addedCoins: increasedCoin);
+              userProvider.coins += increasedCoin;
+              userProvider.refresh();
+            }
+          }
+        }
+        await ExerciseInfoMapper().insert(exerciseInfo);
+        await ScheduledExerciseMapper().delete(mapEntry.key);
+        await Provider.of<DataProvider>(context, listen: false)
+            .loadExerciseInfoData();
+      },
+    );
+  }
+
+  void toAddScheduledExercisePage(BuildContext context) {
+    Navigator.of(context)
+        .push(MaterialPageRoute(builder: (_) => AddScheduledExercisePage()));
+  }
+
+  void changeSizeOfExerciseInfoChartCard(BuildContext context) {
+    DataProvider dataProvider =
+    Provider.of<DataProvider>(context, listen: false);
+    switch (dataProvider.exerciseInfoChartSize) {
+      case 7:
+        dataProvider.exerciseInfoChartSize = 30;
+        break;
+      case 30:
+        dataProvider.exerciseInfoChartSize = 90;
+        break;
+      case 90:
+        dataProvider.exerciseInfoChartSize = 7;
+        break;
+    }
+    dataProvider.notifyListeners();
+
+  }
+}
+
+// view
+class _ExerciseInfoContextView extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return ListView(
+      children: <Widget>[
+        overviewCard(context),
+        exerciseInfoChartCard(context),
+        Divider(),
+        scheduledExerciseCards(context),
+        addScheduledExercise(context),
+      ],
+    );
+  }
+
+  Widget overviewCard(BuildContext context) {
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: true);
+    return BaseCard(
+      title: Text(
+        I18N.of("最近"),
+        style: Theme.of(context).textTheme.titleLarge,
+      ),
+      subtitle: Text(I18N.of("概览")),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: <Widget>[
+          Container(),
+          Icon(
+            Icons.fitness_center,
+            size: 100,
+            color: Theme.of(context).colorScheme.secondary,
+          ),
+          Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: <Widget>[
+              Text(
+                "${I18N.of("待完成计划数")}: ${dataProvider.scheduledExerciseCount}",
+              ),
+              Text(
+                "${I18N.of("7日运动次数")}: ${dataProvider.sevenDayExerciseTimes}",
+              ),
+              Text(
+                "${I18N.of("7日运动总消耗")}: ${dataProvider.sevenDayExerciseTotalKCal.toStringAsFixed(2)} kcal",
+              ),
+            ],
+          ),
+          Container(),
+        ],
+      ),
+    );
+  }
+
+
+  Widget exerciseInfoChartCard(BuildContext context) {
+    ExerciseInfoContextService service =
+    Provider.of<ExerciseInfoContextService>(context, listen: false);
+    DataProvider dataProvider =
+    Provider.of<DataProvider>(context, listen: true);
+    return BaseCard(
+      title: Text(
+        "${I18N.of("卡路里消耗")} (kcal)",
+        style: Theme.of(context).textTheme.titleLarge,
+      ),
+      subtitle: Container(
+        height: 25,
+        child: TextButton(
+          child: Text("< ${dataProvider.exerciseInfoChartSize} >"),
+          onPressed: () => service.changeSizeOfExerciseInfoChartCard(context),
+        ),
+      ),
+      child: DateValueSingleLineChart(
+        size: dataProvider.exerciseInfoChartSize,
+        sports: dataProvider.exerciseInfoFlSpots,
+      ),
+    );
+  }
+
+  Widget addScheduledExercise(BuildContext context) {
+    ExerciseInfoContextService service =
+        Provider.of<ExerciseInfoContextService>(context, listen: false);
+    return Padding(
+      padding: EdgeInsets.only(left: 5, right: 5, bottom: 5),
+      child: ElevatedButton(
+        style: ButtonStyle(
+            backgroundColor: MaterialStateProperty.all(
+                Theme.of(context).colorScheme.secondary),
+            foregroundColor:
+                MaterialStateProperty.all(Theme.of(context).cardColor)),
+        child: Text(I18N.of("添加计划任务")),
+        onPressed: () => service.toAddScheduledExercisePage(context),
+      ),
+    );
+  }
+
+  Widget scheduledExerciseCards(BuildContext context) {
+    ExerciseInfoContextService service =
+        Provider.of<ExerciseInfoContextService>(context, listen: false);
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: true);
+    return Column(
+      children: dataProvider.scheduledExerciseSportInfoList.map((i) {
+        return BaseCard(
+          title: Text(I18N.of("计划任务"),
+            style: Theme.of(context).textTheme.titleLarge,
+          ),
+          child: Row(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+            children: <Widget>[
+              Container(),
+              Icon(
+                Icons.timer,
+                size: 100,
+                color: Theme.of(context).colorScheme.secondary,
+              ),
+              Container(),
+              Column(
+                crossAxisAlignment: CrossAxisAlignment.start,
+                children: <Widget>[
+                  Text("${I18N.of("运动类型")}: ${i.value.getName()}"),
+                  Text(
+                      "${I18N.of("运动时长")}: ${ConvertUtils.fixedDouble(i.key.getQuantity() * 60, 2)} min"),
+                  Text("${I18N.of("消耗")}: ${i.value.getHkCalorie()} h/kcal"),
+                  Text(
+                      "${I18N.of("预计消耗")}: ${ConvertUtils.fixedDouble(i.key.getQuantity() * i.value.getHkCalorie(), 2)} kcal"),
+                  Row(
+                    mainAxisAlignment: MainAxisAlignment.end,
+                    children: <Widget>[
+                      ElevatedButton(
+                 style: ButtonStyle(
+                            backgroundColor: MaterialStateProperty.all(
+                                Theme.of(context).colorScheme.secondary),
+                            foregroundColor: MaterialStateProperty.all(
+                                Theme.of(context).cardColor)),
+                        child: Text(I18N.of("取消")),
+                        onPressed: () =>
+                            service.cancelScheduledExercise(context, i),
+                      ),
+                      Container(
+                        width: 10,
+                      ),
+                      ElevatedButton(
+          style: ButtonStyle(
+                            backgroundColor: MaterialStateProperty.all(
+                                Theme.of(context).colorScheme.secondary),
+                            foregroundColor: MaterialStateProperty.all(
+                                Theme.of(context).cardColor)),
+                        child: Text(I18N.of("完成")),
+                        onPressed: () =>
+                            service.completeScheduledExercise(context, i),
+                      ),
+                    ],
+                  ),
+                ],
+              ),
+              Container(),
+            ],
+          ),
+        );
+      }).toList(),
+    );
+  }
+}

+ 343 - 0
lib/view/context/LifeInfoContext.dart

@@ -0,0 +1,343 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_habit/common/BaseArchitectural.dart';
+import 'package:flutter_habit/common/I18N.dart';
+import 'package:flutter_habit/common/provider/DataProvider.dart';
+import 'package:flutter_habit/common/utils/ConvertUtils.dart';
+import 'package:flutter_habit/view/context/widget/BaseCard.dart';
+import 'package:flutter_habit/view/context/widget/DateTimeMultiLineChart.dart';
+import 'package:flutter_habit/view/context/widget/DateValueSingleLineChart.dart';
+import 'package:provider/provider.dart';
+
+class LifeInfoContext extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return MultiProvider(
+      providers: [
+        ChangeNotifierProvider<LifeInfoContextService>(
+            create: (_) => LifeInfoContextService(context)),
+      ],
+      child: _LifeInfoContextView(),
+    );
+  }
+}
+
+// service
+class LifeInfoContextService extends BaseService {
+  LifeInfoContextService(BuildContext context) : super(context);
+
+  void changeSizeOfSleepTimeChartCard(BuildContext context) {
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: false);
+    switch (dataProvider.sleepTimeChartSize) {
+      case 7:
+        dataProvider.sleepTimeChartSize = 30;
+        break;
+      case 30:
+        dataProvider.sleepTimeChartSize = 90;
+        break;
+      case 90:
+        dataProvider.sleepTimeChartSize = 7;
+        break;
+    }
+    dataProvider.notifyListeners();
+  }
+
+  changeSizeOfInjectKCalChartSize(BuildContext context) {
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: false);
+    switch (dataProvider.injectKCalChartSize) {
+      case 7:
+        dataProvider.injectKCalChartSize = 30;
+        break;
+      case 30:
+        dataProvider.injectKCalChartSize = 90;
+        break;
+      case 90:
+        dataProvider.injectKCalChartSize = 7;
+        break;
+    }
+    dataProvider.notifyListeners();
+  }
+
+  changeSizeOfProgressChartSize(BuildContext context) {
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: false);
+    switch (dataProvider.progressChartSize) {
+      case 7:
+        dataProvider.progressChartSize = 30;
+        break;
+      case 30:
+        dataProvider.progressChartSize = 90;
+        break;
+      case 90:
+        dataProvider.progressChartSize = 7;
+        break;
+    }
+    dataProvider.notifyListeners();
+  }
+
+  void changeSizeOfTimeChartCard(BuildContext context) {
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: false);
+    switch (dataProvider.timeChartSize) {
+      case 7:
+        dataProvider.timeChartSize = 30;
+        break;
+      case 30:
+        dataProvider.timeChartSize = 90;
+        break;
+      case 90:
+        dataProvider.timeChartSize = 7;
+        break;
+    }
+    dataProvider.notifyListeners();
+  }
+
+  void changeSizeOfMoneyChartSize(BuildContext context) {
+    DataProvider dataProvider =
+    Provider.of<DataProvider>(context, listen: false);
+    switch (dataProvider.moneyChartSize) {
+      case 7:
+        dataProvider.moneyChartSize = 30;
+        break;
+      case 30:
+        dataProvider.moneyChartSize = 90;
+        break;
+      case 90:
+        dataProvider.moneyChartSize = 7;
+        break;
+    }
+    dataProvider.notifyListeners();
+  }
+
+  void changeSizeOfEatTimeChartCard(BuildContext context) {
+    DataProvider dataProvider =
+    Provider.of<DataProvider>(context, listen: false);
+    switch (dataProvider.eatTimeChartSize) {
+      case 7:
+        dataProvider.eatTimeChartSize = 30;
+        break;
+      case 30:
+        dataProvider.eatTimeChartSize = 90;
+        break;
+      case 90:
+        dataProvider.eatTimeChartSize = 7;
+        break;
+    }
+    dataProvider.notifyListeners();}
+}
+
+// view
+class _LifeInfoContextView extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    LifeInfoContextService service =
+        Provider.of<LifeInfoContextService>(context, listen: false);
+    return ListView(
+      children: <Widget>[
+        overviewCard(context),
+        sleepTimeChartCard(context),
+        injectKCalChartCard(context),
+        moneyChartCard(context),
+        timeChartCard(context),
+        eatTimeChartCard(context),
+        progressChartCard(context),
+      ],
+    );
+  }
+
+  Widget overviewCard(BuildContext context) {
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: true);
+    return BaseCard(
+      title: Text(
+        I18N.of("今日"),
+        style: Theme.of(context).textTheme.titleLarge,
+      ),
+      subtitle: Text(I18N.of("概览")),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceBetween,
+        children: <Widget>[
+          Container(),
+          Icon(
+            Icons.wb_sunny,
+            size: 100,
+            color: Theme.of(context).colorScheme.secondary,
+          ),
+          Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: <Widget>[
+              Text(
+                "${I18N.of("昨夜睡眠时长")}: ${dataProvider.lastNightSleepTime == null ? I18N.of("无数据") : ConvertUtils.fixedDouble(ConvertUtils.hourFormMilliseconds(dataProvider.lastNightSleepTime), 2)} h",
+              ),
+              Text(
+                "${I18N.of("摄入卡路里总量")}: ${dataProvider.todayInjectKCal.toStringAsFixed(2)} kcal",
+              ),
+              Text(
+                "${I18N.of("消费")}: ${ConvertUtils.fixedDouble(dataProvider.todayMoney, 2)}",
+              ),
+              Text(
+                "${I18N.of("打卡完成度")}: ${ConvertUtils.fixedDouble(dataProvider.todayProgress / 12, 2)}",
+              ),
+            ],
+          ),
+          Container(),
+        ],
+      ),
+    );
+  }
+
+  Widget sleepTimeChartCard(BuildContext context) {
+    LifeInfoContextService service =
+        Provider.of<LifeInfoContextService>(context, listen: false);
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: true);
+    return BaseCard(
+      title: Text(
+        "${I18N.of("睡眠时长")} (h)",
+        style: Theme.of(context).textTheme.titleLarge,
+      ),
+      subtitle: Container(
+        height: 25,
+        child: TextButton(
+          child: Text("< ${dataProvider.sleepTimeChartSize} >"),
+          onPressed: () => service.changeSizeOfSleepTimeChartCard(context),
+        ),
+      ),
+      child: DateValueSingleLineChart(
+        size: dataProvider.sleepTimeChartSize,
+        sports: dataProvider.sleepTimeFlSpots,
+        isEndYesterday: true,
+      ),
+    );
+  }
+
+  Widget injectKCalChartCard(BuildContext context) {
+    LifeInfoContextService service =
+        Provider.of<LifeInfoContextService>(context, listen: false);
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: true);
+    return BaseCard(
+      title: Text(
+        "${I18N.of("摄入卡路里")} (kcal)",
+        style: Theme.of(context).textTheme.titleLarge,
+      ),
+      subtitle: Container(
+        height: 25,
+        child: TextButton(
+          child: Text("< ${dataProvider.injectKCalChartSize} >"),
+          onPressed: () => service.changeSizeOfInjectKCalChartSize(context),
+        ),
+      ),
+      child: DateValueSingleLineChart(
+        size: dataProvider.injectKCalChartSize,
+        sports: dataProvider.injectKCalFlSpots,
+      ),
+    );
+  }
+
+  Widget moneyChartCard(BuildContext context) {
+    LifeInfoContextService service =
+    Provider.of<LifeInfoContextService>(context, listen: false);
+    DataProvider dataProvider =
+    Provider.of<DataProvider>(context, listen: true);
+    return BaseCard(
+      title: Text(
+        "${I18N.of("花费记录")}",
+        style: Theme.of(context).textTheme.titleLarge,
+      ),
+      subtitle: Container(
+        height: 25,
+        child: TextButton(
+          child: Text("< ${dataProvider.moneyChartSize} >"),
+          onPressed: () => service.changeSizeOfMoneyChartSize(context),
+        ),
+      ),
+      child: DateValueSingleLineChart(
+        size: dataProvider.moneyChartSize,
+        sports: dataProvider.moneyFlSpots,
+      ),
+    );
+  }
+
+  Widget progressChartCard(BuildContext context) {
+    LifeInfoContextService service =
+        Provider.of<LifeInfoContextService>(context, listen: false);
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: true);
+    return BaseCard(
+      title: Text(
+        "${I18N.of("每日打卡完成度")}",
+        style: Theme.of(context).textTheme.titleLarge,
+      ),
+      subtitle: Container(
+        height: 25,
+        child: TextButton(
+          child: Text("< ${dataProvider.progressChartSize} >"),
+          onPressed: () => service.changeSizeOfProgressChartSize(context),
+        ),
+      ),
+      child: DateValueSingleLineChart(
+        size: dataProvider.progressChartSize,
+        sports: dataProvider.progressFlSpots,
+      ),
+    );
+  }
+
+  Widget timeChartCard(BuildContext context) {
+    LifeInfoContextService service =
+        Provider.of<LifeInfoContextService>(context, listen: false);
+    DataProvider dataProvider =
+        Provider.of<DataProvider>(context, listen: true);
+    return BaseCard(
+      title: Text(
+        "${I18N.of("睡眠时间")}",
+        style: Theme.of(context).textTheme.titleLarge,
+      ),
+      subtitle: Container(
+        height: 25,
+        child: TextButton(
+          child: Text("< ${dataProvider.timeChartSize} >"),
+          onPressed: () => service.changeSizeOfTimeChartCard(context),
+        ),
+      ),
+      child: DateTimeMultiLineChart(
+        size: dataProvider.timeChartSize,
+        lineNameSportsMap: {
+          I18N.of("起床时间"): dataProvider.getUpTimeFlSpots,
+          I18N.of("午休时间"): dataProvider.midRestTimeFlSpots,
+          I18N.of("睡觉时间"): dataProvider.restTimeFlSpots
+        },
+      ),
+    );
+  }
+
+  Widget eatTimeChartCard(BuildContext context) {
+    LifeInfoContextService service =
+    Provider.of<LifeInfoContextService>(context, listen: false);
+    DataProvider dataProvider =
+    Provider.of<DataProvider>(context, listen: true);
+    return BaseCard(
+      title: Text(
+        "${I18N.of("吃饭时间")}",
+        style: Theme.of(context).textTheme.titleLarge,
+      ),
+      subtitle: Container(
+        height: 25,
+        child: TextButton(
+          child: Text("< ${dataProvider.eatTimeChartSize} >"),
+          onPressed: () => service.changeSizeOfEatTimeChartCard(context),
+        ),
+      ),
+      child: DateTimeMultiLineChart(
+        size: dataProvider.eatTimeChartSize,
+        lineNameSportsMap: {
+          I18N.of("早饭时间"): dataProvider.eatBreakfastTimeFlSpots,
+          I18N.of("午饭时间"): dataProvider.eatLunchTimeFlSpots,
+          I18N.of("晚饭时间"): dataProvider.eatDinnerTimeFlSpots
+        },
+      ),
+    );
+  }
+
+}

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