├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── sia │ │ │ │ └── timefly │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ ├── launch_background.xml │ │ │ └── notification.png │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── store_1024pt.png │ │ │ └── temp.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── settings_aar.gradle ├── assets └── images │ ├── aquarium-水族馆.png │ ├── badminton-player-羽毛球.png │ ├── basketball-篮球.png │ ├── bianji.svg │ ├── bumblebee-熊峰.png │ ├── butterfly-蝴蝶.png │ ├── calendar_view_day.svg │ ├── calendar_view_month.svg │ ├── calendar_view_week.svg │ ├── cat-footprint-猫抓.png │ ├── cute-hamster-可爱仓鼠.png │ ├── cycling-自行车.png │ ├── dinosaur-egg-龙宝宝.png │ ├── dog-狗.png │ ├── dove-鸽子.png │ ├── duigou.svg │ ├── exercise-运动.png │ ├── fanhui.svg │ ├── fishing-钓鱼.png │ ├── flamingo-火烈鸟.png │ ├── guanbi.svg │ ├── habit_check.svg │ ├── icon_contect.svg │ ├── icon_fivestar.svg │ ├── icon_jiaohuan.svg │ ├── icon_theme.svg │ ├── icon_today.svg │ ├── jia.svg │ ├── jian.svg │ ├── jiaohuan.svg │ ├── jump-rope-跳绳.png │ ├── navigation_left.svg │ ├── navigation_right.svg │ ├── parrot-鹦鹉.png │ ├── pilates-普拉提.png │ ├── ping-pong-乒乓球.png │ ├── skateboard-滑板.png │ ├── swan-天鹅.png │ ├── tennis-racquet-网球.png │ ├── time.svg │ ├── treadmill-跑步机.png │ ├── user_icon.jpg │ ├── wancheng.svg │ └── zhouqi.svg ├── fonts ├── MaShanZheng-Regular.ttf ├── Montserrat-Bold.ttf └── Montserrat-Regular.ttf ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── add_habit │ ├── edit_name.dart │ ├── habit_edit_page.dart │ ├── icon_color.dart │ └── modify_change_dialog.dart ├── all_habits │ ├── all_habit_item_view.dart │ ├── all_habit_list_view.dart │ └── all_habits_screen_2.dart ├── app_theme.dart ├── blocs │ ├── bloc_observer.dart │ ├── habit │ │ ├── habit_bloc.dart │ │ ├── habit_event.dart │ │ └── habit_state.dart │ ├── note_bloc.dart │ ├── record_bloc.dart │ ├── theme │ │ ├── theme_bloc.dart │ │ ├── theme_event.dart │ │ └── theme_state.dart │ └── user_bloc.dart ├── db │ └── database_provider.dart ├── detail │ ├── detail_calender_view.dart │ ├── habit_detail_page.dart │ └── habit_detail_views.dart ├── habit_progress │ ├── habit_progress_screen.dart │ ├── progress_rate_views.dart │ └── week_month_chart.dart ├── home_screen.dart ├── login │ └── login_page.dart ├── main.dart ├── mine │ ├── change_theme_screen.dart │ ├── mine_screen.dart │ ├── mine_screen_views.dart │ └── settings_screen.dart ├── models │ ├── complete_time.dart │ ├── habit.dart │ ├── habit_color.dart │ ├── habit_color2.dart │ ├── habit_icon.dart │ ├── habit_list_model.dart │ ├── habit_peroid.dart │ └── user.dart ├── notification │ └── notification_plugin.dart ├── one_day │ ├── habit_check_view.dart │ ├── habit_list_view.dart │ ├── lol_words.dart │ ├── one_day_normal_view.dart │ ├── one_day_rate_view.dart │ └── one_day_screen.dart ├── test.dart ├── utils │ ├── date_util.dart │ ├── flash_helper.dart │ ├── habit_util.dart │ ├── hex_color.dart │ ├── list_utils.dart │ ├── pair.dart │ ├── path_util.dart │ ├── system_util.dart │ └── uuid.dart └── widget │ ├── appbar │ ├── curves.dart │ ├── fluid_button.dart │ ├── fluid_icon.dart │ ├── fluid_icon_data.dart │ └── fluid_nav_bar.dart │ ├── calendar_view.dart │ ├── circle_progress_bar.dart │ ├── clip │ └── bottom_cliper.dart │ ├── custom_edit_field.dart │ ├── float_modal.dart │ └── tab_indicator.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Exceptions to above rules. 44 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 45 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 2ae34518b87dd891355ed6c6ea8cb68c4d52bb9d 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 YaTang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # timefly -- Checkio 2 | 3 | How time flies. 时光荏苒。 4 | 5 | ## 就是想做一个真正喜欢的APP而非demo!!! 6 | 这是一个花了半年业余时间做的一款习惯打卡APP,吸取了市面上很多产品经验。 7 | - 随意定制跟踪周期 8 | - 完成度的报表提示 9 | - 流畅炫酷的动画体验 10 | 11 | ### 主要内容 12 | - 首页展示今天需要完成的习惯 13 | - 所有习惯页面展示所有习惯,点击可以展示当月的完成情况 14 | - 进度页面,有周和月的周期选择,了解在时间范围内完成度情况 15 | - 我的页面,主要是设置,进行主题修改等 16 | 17 | ### 下载Release包体验极致动画效果 18 | 19 | ![QRCode_258](https://user-images.githubusercontent.com/33898039/139042694-ebff2f29-6133-4098-be98-1d831cb758ed.png) 20 | 21 | 22 | 23 | ### 技术栈 24 | - 请查看yaml文件三方依赖 25 | 26 | ### 感谢 27 | - 吸取了很多开源动画例子,这就是Flutter带给我的美妙的旅程,感谢那些无私奉献的大佬(笔芯) 28 | - [reflectly](https://reflectly.app/) 29 | - [flutter_vignettes](https://github.com/gskinnerTeam/flutter_vignettes) 30 | - [Best-Flutter-UI-Templates](https://github.com/mitesh77/Best-Flutter-UI-Templates) 31 | 32 | 33 | 34 | # 一些截图 35 | ## Home 36 | https://user-images.githubusercontent.com/33898039/136545254-3be9630f-9fff-4331-b54d-dec6269c0bda.mp4 37 | 38 | 39 | ## all habits 40 | 41 | https://user-images.githubusercontent.com/33898039/136545286-ea9d0056-e5c8-4428-bae6-ec1ae925799e.mp4 42 | 43 | 44 | ## progress 45 | 46 | 47 | https://user-images.githubusercontent.com/33898039/136545335-6ec83e6c-2f04-4fbd-a33d-b274e40d89cc.mp4 48 | 49 | 50 | ## mine 51 | 52 | 53 | https://user-images.githubusercontent.com/33898039/136545370-f15d7f4f-84f7-4521-8641-6116b1b6e2c5.mp4 54 | 55 | 56 | ## Login 57 | 58 | 59 | https://user-images.githubusercontent.com/33898039/136545393-0891c2b4-6671-4470-beb6-8b6dd9efa1ea.mp4 60 | 61 | 62 | ## add habit 63 | 64 | 65 | https://user-images.githubusercontent.com/33898039/136545432-5a66dd06-9f11-4286-92d6-054bef07d063.mp4 66 | 67 | 68 | ## habit detail 69 | 70 | https://user-images.githubusercontent.com/33898039/136545480-7a0f68a5-b94f-42e7-a12d-7c925bf19844.mp4 71 | 72 | ## add record 73 | 74 | 75 | https://user-images.githubusercontent.com/33898039/136545509-0affb455-046d-49b5-a696-ffa5a75d150b.mp4 76 | 77 | ## change mode 78 | 79 | 80 | https://user-images.githubusercontent.com/33898039/136545621-f8872cc1-cdc5-4af1-94a4-46bb65c6b51c.mp4 81 | 82 | ## change color 83 | 84 | https://user-images.githubusercontent.com/33898039/136545668-c8cc063e-8bbd-4219-8481-44aeae40b26b.mp4 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | checkReleaseBuilds false 37 | disable 'InvalidPackage' 38 | } 39 | 40 | defaultConfig { 41 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 42 | applicationId "com.sia.timefly" 43 | minSdkVersion 16 44 | targetSdkVersion 28 45 | versionCode flutterVersionCode.toInteger() 46 | versionName flutterVersionName 47 | 48 | /* ndk { 49 | abiFilters "armeabi-v7a", "arm64-v8a" 50 | }*/ 51 | } 52 | 53 | buildTypes { 54 | release { 55 | // TODO: Add your own signing config for the release build. 56 | // Signing with the debug keys for now, so `flutter run --release` works. 57 | signingConfig signingConfigs.debug 58 | } 59 | } 60 | } 61 | 62 | 63 | 64 | flutter { 65 | source '../..' 66 | } 67 | 68 | dependencies { 69 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 70 | } 71 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 24 | 28 | 32 | 37 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/sia/timefly/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sia.timefly 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/android/app/src/main/res/drawable/notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/store_1024pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/android/app/src/main/res/mipmap-xxhdpi/store_1024pt.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/temp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/android/app/src/main/res/mipmap-xxhdpi/temp.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | maven{ 5 | url 'https://maven.aliyun.com/repository/central' 6 | } 7 | maven{ 8 | url 'https://maven.aliyun.com/repository/public' 9 | } 10 | maven{ 11 | url 'https://maven.aliyun.com/repository/google' 12 | } 13 | maven{ 14 | url 'https://maven.aliyun.com/repository/gradle-plugin' 15 | } 16 | //google() 17 | //jcenter() 18 | } 19 | 20 | dependencies { 21 | classpath 'com.android.tools.build:gradle:4.0.1' 22 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 23 | } 24 | } 25 | 26 | allprojects { 27 | repositories { 28 | maven{ 29 | url 'https://maven.aliyun.com/repository/central' 30 | } 31 | maven{ 32 | url 'https://maven.aliyun.com/repository/public' 33 | } 34 | maven{ 35 | url 'https://maven.aliyun.com/repository/google' 36 | } 37 | maven{ 38 | url 'https://maven.aliyun.com/repository/gradle-plugin' 39 | } 40 | // google() 41 | //jcenter() 42 | } 43 | } 44 | 45 | rootProject.buildDir = '../build' 46 | subprojects { 47 | project.buildDir = "${rootProject.buildDir}/${project.name}" 48 | } 49 | subprojects { 50 | project.evaluationDependsOn(':app') 51 | } 52 | 53 | task clean(type: Delete) { 54 | delete rootProject.buildDir 55 | } 56 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | #distributionUrl=file:///D:/gradle/gradle-6.1.1-all.zip 7 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /assets/images/aquarium-水族馆.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/aquarium-水族馆.png -------------------------------------------------------------------------------- /assets/images/badminton-player-羽毛球.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/badminton-player-羽毛球.png -------------------------------------------------------------------------------- /assets/images/basketball-篮球.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/basketball-篮球.png -------------------------------------------------------------------------------- /assets/images/bianji.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/bumblebee-熊峰.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/bumblebee-熊峰.png -------------------------------------------------------------------------------- /assets/images/butterfly-蝴蝶.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/butterfly-蝴蝶.png -------------------------------------------------------------------------------- /assets/images/calendar_view_day.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /assets/images/calendar_view_month.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | -------------------------------------------------------------------------------- /assets/images/calendar_view_week.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/cat-footprint-猫抓.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/cat-footprint-猫抓.png -------------------------------------------------------------------------------- /assets/images/cute-hamster-可爱仓鼠.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/cute-hamster-可爱仓鼠.png -------------------------------------------------------------------------------- /assets/images/cycling-自行车.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/cycling-自行车.png -------------------------------------------------------------------------------- /assets/images/dinosaur-egg-龙宝宝.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/dinosaur-egg-龙宝宝.png -------------------------------------------------------------------------------- /assets/images/dog-狗.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/dog-狗.png -------------------------------------------------------------------------------- /assets/images/dove-鸽子.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/dove-鸽子.png -------------------------------------------------------------------------------- /assets/images/duigou.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/exercise-运动.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/exercise-运动.png -------------------------------------------------------------------------------- /assets/images/fanhui.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/fishing-钓鱼.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/fishing-钓鱼.png -------------------------------------------------------------------------------- /assets/images/flamingo-火烈鸟.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/flamingo-火烈鸟.png -------------------------------------------------------------------------------- /assets/images/guanbi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/habit_check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/icon_contect.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/icon_fivestar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/icon_jiaohuan.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/icon_theme.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/icon_today.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/jia.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/jian.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/jiaohuan.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/jump-rope-跳绳.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/jump-rope-跳绳.png -------------------------------------------------------------------------------- /assets/images/navigation_left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/navigation_right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/parrot-鹦鹉.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/parrot-鹦鹉.png -------------------------------------------------------------------------------- /assets/images/pilates-普拉提.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/pilates-普拉提.png -------------------------------------------------------------------------------- /assets/images/ping-pong-乒乓球.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/ping-pong-乒乓球.png -------------------------------------------------------------------------------- /assets/images/skateboard-滑板.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/skateboard-滑板.png -------------------------------------------------------------------------------- /assets/images/swan-天鹅.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/swan-天鹅.png -------------------------------------------------------------------------------- /assets/images/tennis-racquet-网球.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/tennis-racquet-网球.png -------------------------------------------------------------------------------- /assets/images/time.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/treadmill-跑步机.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/treadmill-跑步机.png -------------------------------------------------------------------------------- /assets/images/user_icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/assets/images/user_icon.jpg -------------------------------------------------------------------------------- /assets/images/wancheng.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/zhouqi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fonts/MaShanZheng-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/fonts/MaShanZheng-Regular.ttf -------------------------------------------------------------------------------- /fonts/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/fonts/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /fonts/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/fonts/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_local_notifications (0.0.1): 4 | - Flutter 5 | - fluttertoast (0.0.2): 6 | - Flutter 7 | - Toast 8 | - FMDB (2.7.5): 9 | - FMDB/standard (= 2.7.5) 10 | - FMDB/standard (2.7.5) 11 | - shared_preferences (0.0.1): 12 | - Flutter 13 | - sqflite (0.0.1): 14 | - Flutter 15 | - FMDB (~> 2.7.2) 16 | - Toast (4.0.0) 17 | 18 | DEPENDENCIES: 19 | - Flutter (from `Flutter`) 20 | - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) 21 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) 22 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 23 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 24 | 25 | SPEC REPOS: 26 | trunk: 27 | - FMDB 28 | - Toast 29 | 30 | EXTERNAL SOURCES: 31 | Flutter: 32 | :path: Flutter 33 | flutter_local_notifications: 34 | :path: ".symlinks/plugins/flutter_local_notifications/ios" 35 | fluttertoast: 36 | :path: ".symlinks/plugins/fluttertoast/ios" 37 | shared_preferences: 38 | :path: ".symlinks/plugins/shared_preferences/ios" 39 | sqflite: 40 | :path: ".symlinks/plugins/sqflite/ios" 41 | 42 | SPEC CHECKSUMS: 43 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 44 | flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 45 | fluttertoast: 6122fa75143e992b1d3470f61000f591a798cc58 46 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 47 | shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d 48 | sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 49 | Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 50 | 51 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 52 | 53 | COCOAPODS: 1.10.0 54 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | 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. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | timefly 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/add_habit/edit_name.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:timefly/app_theme.dart'; 3 | import 'package:timefly/utils/pair.dart'; 4 | import 'package:timefly/widget/custom_edit_field.dart'; 5 | 6 | class EditFiledView extends StatefulWidget { 7 | final Mutable content; 8 | 9 | const EditFiledView({ 10 | Key key, 11 | this.content, 12 | }) : super(key: key); 13 | 14 | @override 15 | _EditFiledViewState createState() => _EditFiledViewState(); 16 | } 17 | 18 | class _EditFiledViewState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | backgroundColor: Colors.black54, 23 | body: Center( 24 | child: Container( 25 | padding: EdgeInsets.only(top: 32, bottom: 32), 26 | margin: EdgeInsets.only(left: 32, right: 32), 27 | decoration: BoxDecoration( 28 | shape: BoxShape.rectangle, 29 | borderRadius: BorderRadius.all(Radius.circular(15)), 30 | color: AppTheme.appTheme.cardBackgroundColor()), 31 | child: CustomEditField( 32 | maxLength: 50, 33 | autoFucus: true, 34 | initValue: widget.content.value, 35 | hintText: '记录些什么 ...', 36 | hintTextStyle: AppTheme.appTheme 37 | .hint(fontWeight: FontWeight.normal, fontSize: 16), 38 | textStyle: AppTheme.appTheme 39 | .headline1(fontWeight: FontWeight.normal, fontSize: 16), 40 | minHeight: 100, 41 | containerDecoration: BoxDecoration( 42 | shape: BoxShape.rectangle, 43 | borderRadius: BorderRadius.all(Radius.circular(15)), 44 | color: AppTheme.appTheme.containerBackgroundColor()), 45 | numDecoration: BoxDecoration( 46 | shape: BoxShape.rectangle, 47 | color: AppTheme.appTheme.cardBackgroundColor(), 48 | borderRadius: BorderRadius.all(Radius.circular(15)), 49 | boxShadow: AppTheme.appTheme.containerBoxShadow()), 50 | numTextStyle: AppTheme.appTheme 51 | .themeText(fontWeight: FontWeight.bold, fontSize: 15), 52 | onValueChanged: (value) { 53 | widget.content.value = value; 54 | }, 55 | ), 56 | ), 57 | ), 58 | floatingActionButton: FloatingActionButton( 59 | onPressed: () { 60 | Navigator.of(context).pop(); 61 | }, 62 | child: Icon(Icons.done), 63 | backgroundColor: AppTheme.appTheme.grandientColorStart(), 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/add_habit/icon_color.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | import 'package:timefly/models/habit_color.dart'; 4 | import 'package:timefly/models/habit_icon.dart'; 5 | 6 | import '../app_theme.dart'; 7 | 8 | class IconAndColorPage extends StatefulWidget { 9 | final String selectedIcon; 10 | final Color selectedColor; 11 | 12 | const IconAndColorPage({Key key, this.selectedIcon, this.selectedColor}) 13 | : super(key: key); 14 | 15 | @override 16 | _IconAndColorPageState createState() => _IconAndColorPageState(); 17 | } 18 | 19 | class _IconAndColorPageState extends State { 20 | List icons = []; 21 | HabitIcon _selectIcon; 22 | 23 | List backgroundColors = []; 24 | HabitColor _selectBackgroundColor; 25 | 26 | @override 27 | void initState() { 28 | icons = HabitIcon.getIcons(); 29 | 30 | icons.forEach((icon) { 31 | if (icon.icon == widget.selectedIcon) { 32 | icon.isSelect = true; 33 | _selectIcon = icon; 34 | } else { 35 | icon.isSelect = false; 36 | } 37 | }); 38 | 39 | backgroundColors = HabitColor.getBackgroundColors(); 40 | backgroundColors.forEach((color) { 41 | if (color.color.value == widget.selectedColor.value) { 42 | color.isSelect = true; 43 | _selectBackgroundColor = color; 44 | } else { 45 | color.isSelect = false; 46 | } 47 | }); 48 | 49 | super.initState(); 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | return Center( 55 | child: Container( 56 | height: 450, 57 | width: MediaQuery.of(context).size.width * 0.85, 58 | decoration: BoxDecoration( 59 | shape: BoxShape.rectangle, 60 | borderRadius: BorderRadius.all(Radius.circular(20)), 61 | color: AppTheme.appTheme.cardBackgroundColor(), 62 | ), 63 | child: Column( 64 | children: [ 65 | SizedBox( 66 | height: 20, 67 | ), 68 | Container( 69 | padding: EdgeInsets.only(top: 16, bottom: 16), 70 | height: 240, 71 | child: GridView.builder( 72 | padding: EdgeInsets.only(left: 18), 73 | itemCount: icons.length, 74 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 75 | crossAxisCount: 3, 76 | mainAxisSpacing: 10, 77 | crossAxisSpacing: 10), 78 | itemBuilder: (context, index) { 79 | return GestureDetector( 80 | onTap: () { 81 | setState(() { 82 | icons.forEach((element) { 83 | element.isSelect = false; 84 | }); 85 | icons[index].isSelect = true; 86 | _selectIcon = icons[index]; 87 | }); 88 | }, 89 | child: AnimatedContainer( 90 | duration: Duration(milliseconds: 300), 91 | decoration: BoxDecoration( 92 | borderRadius: BorderRadius.all(Radius.circular(10)), 93 | shape: BoxShape.rectangle, 94 | color: (icons[index].isSelect 95 | ? _selectBackgroundColor.color 96 | : AppTheme.appTheme.containerBackgroundColor())), 97 | alignment: Alignment.center, 98 | child: Image.asset( 99 | icons[index].icon, 100 | width: 40, 101 | height: 40, 102 | ), 103 | ), 104 | ); 105 | }, 106 | scrollDirection: Axis.horizontal, 107 | ), 108 | ), 109 | Container( 110 | height: 130, 111 | padding: EdgeInsets.only(top: 16, bottom: 16), 112 | child: GridView.builder( 113 | scrollDirection: Axis.horizontal, 114 | padding: EdgeInsets.only(left: 18), 115 | itemCount: backgroundColors.length, 116 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 117 | crossAxisCount: 2, 118 | mainAxisSpacing: 16, 119 | crossAxisSpacing: 16), 120 | itemBuilder: (context, index) { 121 | HabitColor habitColor = backgroundColors[index]; 122 | return GestureDetector( 123 | onTap: () { 124 | setState(() { 125 | backgroundColors.forEach((element) { 126 | element.isSelect = false; 127 | }); 128 | backgroundColors[index].isSelect = true; 129 | _selectBackgroundColor = backgroundColors[index]; 130 | }); 131 | }, 132 | child: AnimatedContainer( 133 | alignment: Alignment.center, 134 | width: 50, 135 | height: 50, 136 | decoration: BoxDecoration( 137 | shape: BoxShape.circle, 138 | border: Border.all( 139 | color: habitColor.isSelect 140 | ? habitColor.color 141 | : AppTheme.appTheme 142 | .containerBackgroundColor(), 143 | width: habitColor.isSelect ? 3 : 1.5), 144 | color: Colors.transparent), 145 | child: habitColor.isSelect 146 | ? SizedBox() 147 | : Container( 148 | decoration: BoxDecoration( 149 | shape: BoxShape.circle, 150 | color: habitColor.color), 151 | width: 28, 152 | height: 28), 153 | duration: Duration(milliseconds: 300), 154 | ), 155 | ); 156 | }), 157 | ), 158 | SizedBox( 159 | height: 5, 160 | ), 161 | Container( 162 | height: 40, 163 | child: GestureDetector( 164 | onTap: () { 165 | Map result = Map(); 166 | print(_selectIcon.icon); 167 | result['icon'] = _selectIcon.icon; 168 | result['color'] = _selectBackgroundColor.color; 169 | Navigator.of(context).pop(result); 170 | }, 171 | child: SvgPicture.asset( 172 | 'assets/images/duigou.svg', 173 | width: 35, 174 | height: 35, 175 | color: AppTheme.appTheme.normalColor(), 176 | ), 177 | ), 178 | ) 179 | ], 180 | ), 181 | ), 182 | ); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /lib/add_habit/modify_change_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:alarm_plugin/alarm_event.dart'; 2 | import 'package:alarm_plugin/alarm_plugin.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:timefly/app_theme.dart'; 7 | import 'package:timefly/blocs/habit/habit_bloc.dart'; 8 | import 'package:timefly/blocs/habit/habit_event.dart'; 9 | import 'package:timefly/models/habit.dart'; 10 | import 'package:timefly/utils/flash_helper.dart'; 11 | 12 | class ModifyChangeDialog extends StatefulWidget { 13 | final String title; 14 | final String subTitle; 15 | 16 | const ModifyChangeDialog({ 17 | Key key, 18 | this.title, 19 | this.subTitle, 20 | }) : super(key: key); 21 | 22 | @override 23 | _ModifyChangeDialogState createState() => _ModifyChangeDialogState(); 24 | } 25 | 26 | class _ModifyChangeDialogState extends State 27 | with SingleTickerProviderStateMixin { 28 | AnimationController animationController; 29 | 30 | @override 31 | void initState() { 32 | super.initState(); 33 | animationController = 34 | AnimationController(duration: Duration(milliseconds: 300), vsync: this); 35 | animationController.forward(); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return Center( 41 | child: ScaleTransition( 42 | scale: Tween(begin: 0.2, end: 1).animate(CurvedAnimation( 43 | parent: animationController, curve: Curves.easeOutBack)), 44 | child: Container( 45 | alignment: Alignment.center, 46 | padding: EdgeInsets.all(16), 47 | decoration: BoxDecoration( 48 | shape: BoxShape.rectangle, 49 | borderRadius: BorderRadius.all(Radius.circular(16)), 50 | boxShadow: AppTheme.appTheme.containerBoxShadow(), 51 | color: AppTheme.appTheme.cardBackgroundColor()), 52 | height: 200, 53 | margin: EdgeInsets.only(left: 32, right: 32), 54 | child: Column( 55 | children: [ 56 | SizedBox( 57 | height: 8, 58 | ), 59 | Text( 60 | widget.title, 61 | style: AppTheme.appTheme 62 | .headline1(fontWeight: FontWeight.bold, fontSize: 20) 63 | .copyWith(decoration: TextDecoration.none), 64 | ), 65 | SizedBox( 66 | height: 8, 67 | ), 68 | Text( 69 | widget.subTitle, 70 | style: AppTheme.appTheme 71 | .headline1(fontWeight: FontWeight.normal, fontSize: 16) 72 | .copyWith(decoration: TextDecoration.none), 73 | ), 74 | SizedBox( 75 | height: 16, 76 | ), 77 | GestureDetector( 78 | onTap: () { 79 | Navigator.of(context).pop(); 80 | }, 81 | child: Container( 82 | alignment: Alignment.center, 83 | decoration: BoxDecoration( 84 | gradient: AppTheme.appTheme.containerGradient(), 85 | boxShadow: AppTheme.appTheme.coloredBoxShadow(), 86 | shape: BoxShape.rectangle, 87 | borderRadius: BorderRadius.all(Radius.circular(35))), 88 | width: 150, 89 | height: 50, 90 | child: Text( 91 | '知道啦', 92 | style: AppTheme.appTheme 93 | .headline1( 94 | textColor: Colors.white, 95 | fontWeight: FontWeight.normal, 96 | fontSize: 16) 97 | .copyWith(decoration: TextDecoration.none), 98 | ), 99 | ), 100 | ) 101 | ], 102 | ), 103 | ), 104 | ), 105 | ); 106 | } 107 | 108 | @override 109 | void dispose() { 110 | animationController.dispose(); 111 | super.dispose(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/all_habits/all_habit_list_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:timefly/all_habits/all_habit_item_view.dart'; 3 | import 'package:timefly/db/database_provider.dart'; 4 | import 'package:timefly/models/habit.dart'; 5 | 6 | class AllHabitListView extends StatefulWidget { 7 | final List habits; 8 | 9 | const AllHabitListView({Key key, this.habits}) : super(key: key); 10 | 11 | @override 12 | _AllHabitListViewState createState() => _AllHabitListViewState(); 13 | } 14 | 15 | class _AllHabitListViewState extends State 16 | with AutomaticKeepAliveClientMixin { 17 | final ScrollController scrollController = ScrollController(); 18 | 19 | Habit _selectedHabit; 20 | 21 | double _listPadding = 16; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | super.build(context); 31 | return ClipPath( 32 | clipper: TopClipper(), 33 | child: Container( 34 | margin: EdgeInsets.only(top: 6), 35 | child: ListView.builder( 36 | itemBuilder: (context, index) { 37 | return Container( 38 | margin: EdgeInsets.symmetric( 39 | vertical: _listPadding / 2, 40 | ), 41 | child: AllHabitItemView( 42 | habit: widget.habits[index], 43 | isOpen: widget.habits[index] == _selectedHabit, 44 | onTap: _handleHabitTapped, 45 | ), 46 | ); 47 | }, 48 | itemCount: widget.habits.length, 49 | controller: scrollController, 50 | padding: EdgeInsets.only( 51 | top: 3, 52 | bottom: MediaQuery.of(context).padding.bottom, 53 | )), 54 | ), 55 | ); 56 | } 57 | 58 | void _handleHabitTapped(Habit data) { 59 | setState(() { 60 | //If the same habit was tapped twice, un-select it 61 | if (_selectedHabit == data) { 62 | _selectedHabit = null; 63 | } 64 | //Open tapped habit card and scroll to it 65 | else { 66 | _selectedHabit = data; 67 | var selectedIndex = widget.habits.indexOf(_selectedHabit); 68 | var closedHeight = AllHabitItemView.nominalHeightClosed; 69 | //Calculate scrollTo offset, subtract a bit so we don't end up perfectly at the top 70 | var offset = 71 | selectedIndex * (closedHeight + _listPadding) - closedHeight * .8; 72 | scrollController.animateTo(offset, 73 | duration: Duration(milliseconds: 700), curve: Curves.easeOutQuad); 74 | } 75 | }); 76 | } 77 | 78 | @override 79 | bool get wantKeepAlive => true; 80 | } 81 | 82 | class TopClipper extends CustomClipper { 83 | @override 84 | Path getClip(Size size) { 85 | var path = Path(); 86 | path.lineTo(0, 20); 87 | var point0 = Offset(size.width / 2, 0); 88 | var point1 = Offset(size.width, 20); 89 | path.quadraticBezierTo(point0.dx, point0.dy, point1.dx, point1.dy); 90 | path.lineTo(size.width, size.height); 91 | path.lineTo(0, size.height); 92 | return path; 93 | } 94 | 95 | @override 96 | bool shouldReclip(CustomClipper oldClipper) { 97 | return false; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/blocs/bloc_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | 3 | class SimpleBlocObserver extends BlocObserver { 4 | @override 5 | void onEvent(Bloc bloc, Object event) { 6 | super.onEvent(bloc, event); 7 | print(event); 8 | } 9 | 10 | @override 11 | void onTransition(Bloc bloc, Transition transition) { 12 | super.onTransition(bloc, transition); 13 | print(transition); 14 | } 15 | 16 | @override 17 | void onError(BlocBase bloc, Object error, StackTrace stackTrace) { 18 | print(error); 19 | super.onError(bloc, error, stackTrace); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/blocs/habit/habit_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:timefly/blocs/habit/habit_event.dart'; 3 | import 'package:timefly/blocs/habit/habit_state.dart'; 4 | import 'package:timefly/db/database_provider.dart'; 5 | import 'package:timefly/models/habit.dart'; 6 | import 'package:timefly/models/user.dart'; 7 | 8 | class HabitsBloc extends Bloc { 9 | ///初始化状态为正在加载 10 | HabitsBloc() : super(HabitsLoadInProgress()) { 11 | on(_mapHabitsLoadToState); 12 | on(_mapHabitsAddToState); 13 | on(_mapHabitUpdateToState); 14 | } 15 | 16 | void _mapHabitsLoadToState( 17 | HabitsLoad event, Emitter emit) async { 18 | try { 19 | if (!SessionUtils.sharedInstance().isLogin()) { 20 | emit(HabitLoadSuccess([])); 21 | return; 22 | } 23 | List habits = await DatabaseProvider.db.getAllHabits(); 24 | print(habits); 25 | emit(HabitLoadSuccess(habits)); 26 | } catch (e) { 27 | print(e); 28 | emit(HabitsLodeFailure()); 29 | } 30 | } 31 | 32 | void _mapHabitsAddToState(HabitsAdd event, Emitter emit) { 33 | if (state is HabitLoadSuccess) { 34 | final List habits = List.from((state as HabitLoadSuccess).habits) 35 | ..add(event.habit); 36 | emit(HabitLoadSuccess(habits)); 37 | DatabaseProvider.db.insert(event.habit); 38 | } 39 | } 40 | 41 | void _mapHabitUpdateToState(HabitUpdate event, Emitter emit) { 42 | if (state is HabitLoadSuccess) { 43 | final List habits = (state as HabitLoadSuccess) 44 | .habits 45 | .map((habit) => habit.id == event.habit.id ? event.habit : habit) 46 | .toList(); 47 | emit(HabitLoadSuccess(habits)); 48 | DatabaseProvider.db.update(event.habit); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/blocs/habit/habit_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:timefly/models/habit.dart'; 3 | 4 | ///驱动UI的事件,数据库操作,将事件转化为包含数据的state返回 5 | class HabitsEvent extends Equatable { 6 | const HabitsEvent(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | ///加载数据库数据事件 13 | class HabitsLoad extends HabitsEvent {} 14 | 15 | ///添加一个数据 16 | class HabitsAdd extends HabitsEvent { 17 | final Habit habit; 18 | 19 | HabitsAdd(this.habit); 20 | 21 | @override 22 | List get props => [habit]; 23 | } 24 | 25 | ///更新 26 | class HabitUpdate extends HabitsEvent { 27 | final Habit habit; 28 | 29 | HabitUpdate(this.habit); 30 | 31 | @override 32 | List get props => [habit]; 33 | } 34 | -------------------------------------------------------------------------------- /lib/blocs/habit/habit_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:timefly/models/habit.dart'; 3 | 4 | ///习惯列表状态 5 | abstract class HabitsState extends Equatable { 6 | const HabitsState(); 7 | 8 | @override 9 | List get props => []; 10 | } 11 | 12 | ///正在加载习惯列表 显示 loading样式 13 | class HabitsLoadInProgress extends HabitsState {} 14 | 15 | ///习惯加载完成 显示习惯列表 16 | class HabitLoadSuccess extends HabitsState { 17 | final List habits; 18 | 19 | const HabitLoadSuccess(this.habits); 20 | 21 | @override 22 | List get props => [habits]; 23 | } 24 | 25 | ///习惯加载失败,显示异常UI 26 | class HabitsLodeFailure extends HabitsState { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /lib/blocs/note_bloc.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/designDo/flutter-checkio/1441787d5474214b8dcc260b25b5cc3a40093f59/lib/blocs/note_bloc.dart -------------------------------------------------------------------------------- /lib/blocs/record_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:timefly/blocs/habit/habit_bloc.dart'; 4 | import 'package:timefly/blocs/habit/habit_event.dart'; 5 | import 'package:timefly/blocs/habit/habit_state.dart'; 6 | import 'package:timefly/db/database_provider.dart'; 7 | import 'package:timefly/models/habit.dart'; 8 | 9 | class RecordState extends Equatable { 10 | const RecordState(); 11 | 12 | @override 13 | List get props => []; 14 | } 15 | 16 | class RecordLoadSuccess extends RecordState { 17 | final List records; 18 | 19 | RecordLoadSuccess(this.records); 20 | 21 | @override 22 | List get props => [records]; 23 | } 24 | 25 | class RecordLoadInProgress extends RecordState {} 26 | 27 | class RecordLoadFailure extends RecordState {} 28 | 29 | class RecordEvent extends Equatable { 30 | const RecordEvent(); 31 | 32 | @override 33 | List get props => []; 34 | } 35 | 36 | ///加载数据库数据事件 37 | class RecordLoad extends RecordEvent { 38 | final String habitId; 39 | final DateTime start; 40 | final DateTime end; 41 | 42 | RecordLoad(this.habitId, this.start, this.end); 43 | 44 | @override 45 | List get props => [habitId, start, end]; 46 | } 47 | 48 | ///添加一个数据 49 | class RecordAdd extends RecordEvent { 50 | final HabitRecord record; 51 | 52 | RecordAdd(this.record); 53 | 54 | @override 55 | List get props => [record]; 56 | } 57 | 58 | class RecordDelete extends RecordEvent { 59 | final String habitId; 60 | final int time; 61 | 62 | RecordDelete(this.habitId, this.time); 63 | 64 | @override 65 | List get props => [habitId, time]; 66 | } 67 | 68 | ///更新 69 | class RecordUpdate extends RecordEvent { 70 | final HabitRecord record; 71 | 72 | RecordUpdate(this.record); 73 | 74 | @override 75 | List get props => [record]; 76 | } 77 | 78 | class RecordBloc extends Bloc { 79 | final HabitsBloc habitsBloc; 80 | 81 | ///初始化状态为正在加载 82 | RecordBloc(this.habitsBloc) : super(RecordLoadInProgress()); 83 | 84 | @override 85 | Stream mapEventToState(RecordEvent event) async* { 86 | if (event is RecordLoad) { 87 | yield* _mapRecordLoadToState(event); 88 | } else if (event is RecordAdd) { 89 | yield* _mapRecordAddToState(event); 90 | } else if (event is RecordUpdate) { 91 | yield* _mapRecordUpdateToState(event); 92 | } else if (event is RecordDelete) { 93 | yield* _mapRecordDeleteToState(event); 94 | } 95 | } 96 | 97 | Stream _mapRecordLoadToState(RecordLoad event) async* { 98 | try { 99 | List records; 100 | if (habitsBloc.state is HabitLoadSuccess) { 101 | Habit habit = (habitsBloc.state as HabitLoadSuccess) 102 | .habits 103 | .firstWhere((habit) => habit.id == event.habitId); 104 | records = habit.records; 105 | 106 | if (records != null && records.length > 0) { 107 | if (event.start != null && event.end != null) { 108 | records = records 109 | .where((element) => 110 | element.time > event.start.millisecondsSinceEpoch && 111 | element.time < event.end.millisecondsSinceEpoch) 112 | .toList(); 113 | records.sort((a, b) => b.time - a.time); 114 | } else { 115 | records.sort((a, b) => b.time - a.time); 116 | } 117 | } 118 | yield RecordLoadSuccess(records); 119 | return; 120 | } 121 | records = []; 122 | yield RecordLoadSuccess(records); 123 | } catch (_) { 124 | yield RecordLoadFailure(); 125 | } 126 | } 127 | 128 | Stream _mapRecordAddToState(RecordAdd event) async* { 129 | try { 130 | if (state is RecordLoadSuccess) { 131 | final List records = 132 | List.from((state as RecordLoadSuccess).records) 133 | ..insert(0, event.record); 134 | DatabaseProvider.db.insertHabitRecord(event.record); 135 | if (habitsBloc.state is HabitLoadSuccess) { 136 | Habit currentHabit = (habitsBloc.state as HabitLoadSuccess) 137 | .habits 138 | .firstWhere((habit) => habit.id == event.record.habitId); 139 | habitsBloc.add(HabitUpdate(currentHabit.copyWith( 140 | records: List.from(currentHabit.records)..add(event.record)))); 141 | } 142 | yield RecordLoadSuccess(records); 143 | } 144 | } catch (e) {} 145 | } 146 | 147 | Stream _mapRecordUpdateToState(RecordUpdate event) async* { 148 | try { 149 | if (state is RecordLoadSuccess) { 150 | final List records = (state as RecordLoadSuccess) 151 | .records 152 | .map((record) => 153 | record.time == event.record.time ? event.record : record) 154 | .toList(); 155 | yield RecordLoadSuccess(records); 156 | DatabaseProvider.db.updateHabitRecord(event.record); 157 | 158 | if (habitsBloc.state is HabitLoadSuccess) { 159 | Habit currentHabit = (habitsBloc.state as HabitLoadSuccess) 160 | .habits 161 | .firstWhere((habit) => habit.id == event.record.habitId); 162 | List currentHabitRecords = 163 | List.from(currentHabit.records); 164 | for (int i = 0; i < currentHabitRecords.length; i++) { 165 | if (currentHabitRecords[i].time == event.record.time) { 166 | currentHabitRecords[i] = event.record; 167 | } 168 | } 169 | habitsBloc.add( 170 | HabitUpdate(currentHabit.copyWith(records: currentHabitRecords))); 171 | } 172 | } 173 | } catch (e) {} 174 | } 175 | 176 | Stream _mapRecordDeleteToState(RecordDelete event) async* { 177 | try { 178 | if (state is RecordLoadSuccess) { 179 | final List records = 180 | List.from((state as RecordLoadSuccess).records) 181 | ..removeWhere((record) => record.time == event.time); 182 | yield RecordLoadSuccess(records); 183 | DatabaseProvider.db.deleteHabitRecord(event.habitId, event.time); 184 | 185 | if (habitsBloc.state is HabitLoadSuccess) { 186 | Habit currentHabit = (habitsBloc.state as HabitLoadSuccess) 187 | .habits 188 | .firstWhere((habit) => habit.id == event.habitId); 189 | habitsBloc.add(HabitUpdate(currentHabit.copyWith( 190 | records: List.from(currentHabit.records) 191 | ..removeWhere((record) => record.time == event.time)))); 192 | } 193 | } 194 | } catch (e) {} 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /lib/blocs/theme/theme_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | import 'package:timefly/app_theme.dart'; 4 | import 'package:timefly/blocs/theme/theme_event.dart'; 5 | import 'package:timefly/blocs/theme/theme_state.dart'; 6 | 7 | class ThemeBloc extends Bloc { 8 | ThemeBloc() 9 | : super(ThemeState( 10 | AppThemeMode.Light, AppThemeColorMode.Blue, AppFontMode.Roboto)) { 11 | AppTheme.appTheme 12 | .setThemeState(state.themeMode, state.themeColorMode, state.fontMode); 13 | } 14 | 15 | @override 16 | Stream mapEventToState(ThemeEvent event) async* { 17 | if (event is ThemeChangeEvent) { 18 | ThemeState state = 19 | ThemeState(event.themeMode, event.themeColorMode, event.fontMode); 20 | AppTheme.appTheme 21 | .setThemeState(state.themeMode, state.themeColorMode, state.fontMode); 22 | yield state; 23 | } else if (event is ThemeLoadEvnet) { 24 | SharedPreferences shared = await SharedPreferences.getInstance(); 25 | AppThemeMode appThemeMode = 26 | getInitThemeMode(shared.getString(THEME_MODE)); 27 | AppThemeColorMode colorMode = 28 | getInitColorMode(shared.getString(COLOR_MODE)); 29 | ThemeState state = 30 | ThemeState(appThemeMode, colorMode, AppFontMode.Roboto); 31 | AppTheme.appTheme 32 | .setThemeState(state.themeMode, state.themeColorMode, state.fontMode); 33 | yield state; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/blocs/theme/theme_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:timefly/app_theme.dart'; 3 | 4 | abstract class ThemeEvent extends Equatable { 5 | const ThemeEvent(); 6 | } 7 | 8 | class ThemeChangeEvent extends ThemeEvent { 9 | ThemeChangeEvent(this.themeMode, this.themeColorMode, this.fontMode); 10 | 11 | @override 12 | List get props => [themeMode, themeColorMode, fontMode]; 13 | 14 | final AppThemeMode themeMode; 15 | final AppThemeColorMode themeColorMode; 16 | final AppFontMode fontMode; 17 | } 18 | 19 | class ThemeLoadEvnet extends ThemeEvent { 20 | @override 21 | List get props => []; 22 | } 23 | -------------------------------------------------------------------------------- /lib/blocs/theme/theme_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../app_theme.dart'; 4 | 5 | class ThemeState extends Equatable { 6 | ThemeState(this.themeMode, this.themeColorMode, this.fontMode); 7 | 8 | @override 9 | List get props => [themeMode, themeColorMode, fontMode]; 10 | 11 | final AppThemeMode themeMode; 12 | final AppThemeColorMode themeColorMode; 13 | final AppFontMode fontMode; 14 | } 15 | -------------------------------------------------------------------------------- /lib/blocs/user_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:timefly/blocs/habit/habit_bloc.dart'; 4 | import 'package:timefly/blocs/habit/habit_event.dart'; 5 | import 'package:timefly/db/database_provider.dart'; 6 | import 'package:timefly/models/user.dart'; 7 | 8 | class UserState extends Equatable { 9 | const UserState(); 10 | 11 | @override 12 | List get props => []; 13 | } 14 | 15 | class UserLoadSuccess extends UserState { 16 | final User user; 17 | 18 | UserLoadSuccess(this.user); 19 | 20 | @override 21 | List get props => [user]; 22 | } 23 | 24 | class UserLoadingInProgress extends UserState {} 25 | 26 | class UserEvent extends Equatable { 27 | const UserEvent(); 28 | 29 | @override 30 | List get props => []; 31 | } 32 | 33 | class UserLoginEvent extends UserEvent { 34 | final User user; 35 | 36 | UserLoginEvent(this.user); 37 | 38 | @override 39 | List get props => [user]; 40 | } 41 | 42 | class UserLogoutEvent extends UserEvent {} 43 | 44 | class UserLoadEvent extends UserEvent {} 45 | 46 | class UserBloc extends Bloc { 47 | final HabitsBloc habitsBloc; 48 | 49 | UserBloc(this.habitsBloc) : super(UserLoadSuccess(null)); 50 | 51 | @override 52 | Stream mapEventToState(UserEvent event) async* { 53 | if (event is UserLoginEvent) { 54 | yield* _mapUserLoginToState(event); 55 | } else if (event is UserLogoutEvent) { 56 | yield* _mapUserLogoutToState(event); 57 | } else if (event is UserLoadEvent) { 58 | yield* _mapUserLoadState(event); 59 | } 60 | } 61 | 62 | Stream _mapUserLoginToState(UserLoginEvent event) async* { 63 | await DatabaseProvider.db.saveUser(event.user); 64 | // SessionUtils.login(event.user); 65 | yield UserLoadSuccess(event.user); 66 | habitsBloc.add(HabitsLoad()); 67 | } 68 | 69 | Stream _mapUserLogoutToState(UserLogoutEvent event) async* { 70 | await DatabaseProvider.db.deleteUser(); 71 | // SessionUtils.logout(); 72 | yield UserLoadSuccess(null); 73 | habitsBloc.add(HabitsLoad()); 74 | } 75 | 76 | Stream _mapUserLoadState(UserLoadEvent event) async* { 77 | User user = await DatabaseProvider.db.getCurrentUser(); 78 | // SessionUtils.login(user); 79 | yield UserLoadSuccess(user); 80 | habitsBloc.add(HabitsLoad()); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/db/database_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:path/path.dart'; 2 | import 'package:sqflite/sqflite.dart'; 3 | import 'package:timefly/models/habit.dart'; 4 | import 'package:timefly/models/user.dart'; 5 | 6 | class DatabaseProvider { 7 | DatabaseProvider._(); 8 | 9 | static final DatabaseProvider db = DatabaseProvider._(); 10 | 11 | static const String RECORDS = 'records'; 12 | 13 | ///my database 14 | Database _database; 15 | 16 | Future get database async { 17 | if (_database != null) { 18 | return _database; 19 | } 20 | 21 | _database = await createDatabase(); 22 | 23 | return _database; 24 | } 25 | 26 | Future createDatabase() async { 27 | String dbPath = await getDatabasesPath(); 28 | 29 | return await openDatabase( 30 | join(dbPath, 'habitDB.db'), 31 | version: 1, 32 | onCreate: (Database database, int version) async { 33 | print("Creating habit table"); 34 | await database.execute( 35 | "CREATE TABLE habits (" 36 | "id TEXT," 37 | "name TEXT," 38 | "iconPath TEXT," 39 | "mainColor INTEGER," 40 | "mark TEXT," 41 | "remindTimes TEXT," 42 | "period INTEGER," 43 | "completeTime INTEGER," 44 | "completeDays TEXT," 45 | "createTime INTEGER," 46 | "modifyTime INTEGER," 47 | "completed INTEGER," 48 | "doNum INTEGER" 49 | ")", 50 | ); 51 | await database.execute("CREATE TABLE records (" 52 | "time INTEGER," 53 | "habitId TEXT," 54 | "content TEXT" 55 | ")"); 56 | 57 | await database.execute("CREATE TABLE user (" 58 | "username TEXT," 59 | "phone TEXT," 60 | "id TEXT" 61 | ")"); 62 | }, 63 | ); 64 | } 65 | 66 | Future getCurrentUser() async { 67 | final db = await database; 68 | var users = await db.query("user"); 69 | if (users.isEmpty) { 70 | return null; 71 | } 72 | return User.fromJson(users[0]); 73 | } 74 | 75 | Future saveUser(User user) async { 76 | final db = await database; 77 | var index = await db.insert('user', user.toJson()); 78 | return index > 0; 79 | } 80 | 81 | Future deleteUser() async { 82 | final db = await database; 83 | var index = await db.delete( 84 | 'user', 85 | ); 86 | return index > 0; 87 | } 88 | 89 | Future updateUser(User user) async { 90 | final db = await database; 91 | int change = await db 92 | .update('user', user.toJson(), where: 'id = ?', whereArgs: [user.id]); 93 | return change > 0; 94 | } 95 | 96 | Future> getAllHabits() async { 97 | final db = await database; 98 | var habits = await db.query('habits'); 99 | List newHabitList = []; 100 | 101 | for (Map element in habits) { 102 | final habitId = element["id"]?.toString(); 103 | var records = await getHabitRecords(habitId); 104 | Habit habit = Habit.fromJson(element, records: records); 105 | newHabitList.add(habit); 106 | } 107 | newHabitList.sort((a, b) => b.createTime - a.createTime); 108 | return newHabitList; 109 | } 110 | 111 | ///获取带有打卡记录的所有习惯 112 | Future> getHabitsWithRecords() async { 113 | final db = await database; 114 | var habits = await db.query('habits'); 115 | List newHabitList = []; 116 | 117 | for (Map element in habits) { 118 | final habitId = element["id"]?.toString(); 119 | var records = await getHabitRecords(habitId); 120 | Habit habit = Habit.fromJson(element, records: records); 121 | newHabitList.add(habit); 122 | } 123 | newHabitList.sort((a, b) => b.createTime - a.createTime); 124 | return newHabitList; 125 | } 126 | 127 | Future> getHabitsWithCompleteTime(int completeTime) async { 128 | final db = await database; 129 | var habits = await db 130 | .query('habits', where: 'completeTime = ?', whereArgs: [completeTime]); 131 | List newHabitList = []; 132 | habits.forEach((element) { 133 | newHabitList.add(Habit.fromJson(element)); 134 | }); 135 | newHabitList.sort((a, b) => b.createTime - a.createTime); 136 | return newHabitList; 137 | } 138 | 139 | /// 根据 habitId和时间范围筛选出符合条件的记录 140 | Future> getHabitRecords(String habitId, 141 | {DateTime start, DateTime end}) async { 142 | final db = await database; 143 | var records = List.from( 144 | await db.query(RECORDS, where: 'habitId = ?', whereArgs: [habitId])); 145 | List habitRecords = []; 146 | if (records != null && records.length > 0) { 147 | if (start != null && end != null) { 148 | records = records 149 | .where((element) => 150 | element['time'] > start.millisecondsSinceEpoch && 151 | element['time'] < end.millisecondsSinceEpoch) 152 | .toList(); 153 | records.sort((a, b) => b['time'] - a['time']); 154 | } else { 155 | records.sort((a, b) => b['time'] - a['time']); 156 | } 157 | records.forEach((element) { 158 | habitRecords.add(HabitRecord.fromJson(element)); 159 | }); 160 | } 161 | return habitRecords; 162 | } 163 | 164 | ///获取天数分类习惯个数,用于’我的一天‘页面分类 165 | Future getPeriodHabitsSize(int period) async { 166 | final db = await database; 167 | var habits = 168 | await db.query('habits', where: 'period = ?', whereArgs: [period]); 169 | return habits.length; 170 | } 171 | 172 | Future insert(Habit habit) async { 173 | final db = await database; 174 | var queryHabits = 175 | await db.query('habits', where: 'id = ?', whereArgs: [habit.id]); 176 | bool exist = false; 177 | queryHabits.forEach((element) { 178 | if (element['id'] == habit.id) { 179 | exist = true; 180 | } 181 | }); 182 | if (exist) { 183 | print('habit has exits'); 184 | return null; 185 | } 186 | await db.insert('habits', habit.toJson()); 187 | return habit; 188 | } 189 | 190 | Future insertHabitRecord(HabitRecord record) async { 191 | final db = await database; 192 | int index = await db.insert(RECORDS, record.toJson()); 193 | return index > 0; 194 | } 195 | 196 | Future deleteHabitRecord(String habitId, int time) async { 197 | final db = await database; 198 | int index = await db.delete(RECORDS, 199 | where: 'habitId = ? and time = ?', whereArgs: [habitId, time]); 200 | return index > 0; 201 | } 202 | 203 | Future updateHabitRecord(HabitRecord habitRecord) async { 204 | final db = await database; 205 | int change = await db.update(RECORDS, habitRecord.toJson(), 206 | where: 'time = ?', whereArgs: [habitRecord.time]); 207 | return change > 0; 208 | } 209 | 210 | ///更新 211 | Future update(Habit habit) async { 212 | final db = await database; 213 | int change = await db.update('habits', habit.toJson(), 214 | where: 'id = ?', whereArgs: [habit.id]); 215 | return change > 0; 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /lib/detail/detail_calender_view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:timefly/models/habit.dart'; 4 | import 'package:timefly/models/habit_peroid.dart'; 5 | import 'package:timefly/one_day/habit_check_view.dart'; 6 | import 'package:timefly/utils/date_util.dart'; 7 | import 'package:timefly/utils/flash_helper.dart'; 8 | import 'package:timefly/utils/pair.dart'; 9 | import 'package:timefly/widget/float_modal.dart'; 10 | 11 | import '../app_theme.dart'; 12 | 13 | ///详情页面的 月 面板 14 | /// 15 | class HabitDetailCalendarView extends StatefulWidget { 16 | final String habitId; 17 | final int createTime; 18 | final Color color; 19 | final Map> records; 20 | final List days; 21 | final int period; 22 | final List completeDays; 23 | 24 | const HabitDetailCalendarView( 25 | {Key key, 26 | this.habitId, 27 | this.color, 28 | this.records, 29 | this.days, 30 | this.createTime, 31 | this.completeDays, 32 | this.period}) 33 | : super(key: key); 34 | 35 | @override 36 | _HabitDetailCalendarViewState createState() => 37 | _HabitDetailCalendarViewState(); 38 | } 39 | 40 | class _HabitDetailCalendarViewState extends State { 41 | List days; 42 | 43 | @override 44 | void initState() { 45 | days = widget.days; 46 | super.initState(); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return Container( 52 | child: GridView.builder( 53 | physics: NeverScrollableScrollPhysics(), 54 | padding: EdgeInsets.only(left: .3, right: .3), 55 | itemCount: days.length, 56 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 57 | crossAxisCount: 7, childAspectRatio: 1.5, mainAxisSpacing: 5), 58 | itemBuilder: (context, index) { 59 | DateTime day = days[index]; 60 | Pair2 contains = containsDay(day); 61 | if (day == null) { 62 | if (index < 7) { 63 | return Container( 64 | alignment: Alignment.center, 65 | child: Text( 66 | '${DateUtil.getWeekendString(index + 1)}', 67 | style: AppTheme.appTheme.numHeadline1( 68 | textColor: Colors.black, 69 | fontWeight: FontWeight.w500, 70 | fontSize: 15), 71 | ), 72 | ); 73 | } 74 | return Container(); 75 | } 76 | return GestureDetector( 77 | onTap: () async { 78 | if (DateUtil.isFuture(day)) { 79 | FlashHelper.toast(context, '超出时间范围'); 80 | return; 81 | } 82 | if (DateUtil.isLast(day, 83 | DateTime.fromMillisecondsSinceEpoch(widget.createTime))) { 84 | FlashHelper.toast(context, '超出创建时间'); 85 | return; 86 | } 87 | if (widget.period != HabitPeriod.month && 88 | widget.completeDays.length != 7 && 89 | !widget.completeDays.contains(day.weekday)) { 90 | FlashHelper.toast(context, '不在记录周期'); 91 | return; 92 | } 93 | showFloatingModalBottomSheet( 94 | barrierColor: Colors.black87, 95 | context: context, 96 | builder: (context) { 97 | return HabitCheckView( 98 | habitId: widget.habitId, 99 | isFromDetail: true, 100 | start: DateUtil.startOfDay(day), 101 | end: DateUtil.endOfDay(day), 102 | ); 103 | }); 104 | }, 105 | onDoubleTap: () {}, 106 | child: Container( 107 | alignment: Alignment.center, 108 | child: AspectRatio( 109 | aspectRatio: 1, 110 | child: CustomPaint( 111 | painter: ContainerPainter(contains.s, contains.t, 112 | _lineHeight() / 2, widget.color), 113 | child: Container( 114 | alignment: Alignment.center, 115 | child: Text( 116 | '${DateUtil.isToday(day.millisecondsSinceEpoch) ? '今' : day.day}', 117 | style: AppTheme.appTheme.numHeadline1( 118 | textColor: contains.s ? Colors.white : Colors.black, 119 | fontWeight: FontWeight.bold, 120 | fontSize: 15), 121 | ), 122 | ), 123 | ), 124 | ), 125 | ), 126 | ); 127 | }), 128 | ); 129 | } 130 | 131 | Pair2 containsDay(DateTime date) { 132 | if (date == null) { 133 | return Pair2(false, 0); 134 | } 135 | bool contain = false; 136 | int count = 0; 137 | if (widget.records == null || widget.records.length == 0) { 138 | contain = false; 139 | } else if (widget.records 140 | .containsKey('${date.year}-${date.month}-${date.day}')) { 141 | contain = true; 142 | count = widget.records['${date.year}-${date.month}-${date.day}'].length; 143 | } 144 | return Pair2(contain, count); 145 | } 146 | 147 | double _lineHeight() { 148 | return ((MediaQuery.of(context).size.width - 20 * 2) / 7) / 1.5; 149 | } 150 | } 151 | 152 | class ContainerPainter extends CustomPainter { 153 | final int count; 154 | final bool needPaint; 155 | final double radius; 156 | final Color color; 157 | 158 | ContainerPainter(this.needPaint, this.count, this.radius, this.color); 159 | 160 | @override 161 | void paint(Canvas canvas, Size size) { 162 | final Offset center = size.center(Offset.zero); 163 | final Paint paint = Paint()..color = needPaint ? color : Colors.transparent; 164 | canvas.drawCircle(center, radius - 2, paint); 165 | 166 | for (int i = 0; i < count; i++) { 167 | canvas.drawCircle( 168 | center + 169 | Offset((radius + 3) * cos((-2 + i) * pi / 6), 170 | (radius + 3) * sin((-2 + i) * pi / 6)), 171 | 3, 172 | paint); 173 | } 174 | } 175 | 176 | @override 177 | bool shouldRepaint(CustomPainter oldDelegate) { 178 | ContainerPainter oldPinter = oldDelegate as ContainerPainter; 179 | return oldPinter.needPaint != this.needPaint || 180 | oldPinter.count != this.count; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /lib/detail/habit_detail_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:flutter_svg/svg.dart'; 6 | import 'package:timefly/add_habit/habit_edit_page.dart'; 7 | import 'package:timefly/app_theme.dart'; 8 | import 'package:timefly/blocs/habit/habit_bloc.dart'; 9 | import 'package:timefly/blocs/habit/habit_state.dart'; 10 | import 'package:timefly/detail/habit_detail_views.dart'; 11 | import 'package:timefly/models/habit.dart'; 12 | import 'package:timefly/utils/habit_util.dart'; 13 | import 'package:timefly/utils/system_util.dart'; 14 | 15 | ///detail page 16 | class HabitDetailPage extends StatefulWidget { 17 | final String habitId; 18 | 19 | const HabitDetailPage({Key key, this.habitId}) : super(key: key); 20 | 21 | @override 22 | _HabitDetailPageState createState() => _HabitDetailPageState(); 23 | } 24 | 25 | class _HabitDetailPageState extends State 26 | with SingleTickerProviderStateMixin { 27 | ScrollController _controller; 28 | AnimationController _animationController; 29 | 30 | @override 31 | void initState() { 32 | _controller = ScrollController(); 33 | _animationController = AnimationController( 34 | duration: Duration(milliseconds: 1000), vsync: this); 35 | Future.delayed(Duration(milliseconds: 300), () { 36 | _animationController.forward(); 37 | }); 38 | super.initState(); 39 | } 40 | 41 | @override 42 | void dispose() { 43 | _controller.dispose(); 44 | _animationController.dispose(); 45 | super.dispose(); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return AnnotatedRegion( 51 | value: SystemUtil.getSystemUiOverlayStyle( 52 | AppTheme.appTheme.isDark() ? Brightness.light : Brightness.dark), 53 | child: BlocBuilder( 54 | builder: (context, state) { 55 | if (state is HabitsLoadInProgress) { 56 | return CircularProgressIndicator(); 57 | } 58 | if (state is HabitLoadSuccess) { 59 | final Habit habit = state.habits 60 | .firstWhere((element) => element.id == widget.habitId); 61 | return _body(habit); 62 | } 63 | return Container(); 64 | }, 65 | ), 66 | ); 67 | } 68 | 69 | Widget _body(Habit habit) { 70 | return Scaffold( 71 | appBar: AppBar( 72 | leading: IconButton( 73 | iconSize: 32, 74 | padding: EdgeInsets.all(14), 75 | icon: SvgPicture.asset( 76 | 'assets/images/fanhui.svg', 77 | color: Colors.white, 78 | ), 79 | onPressed: () { 80 | Navigator.of(context).pop(); 81 | }), 82 | backgroundColor: AppTheme.appTheme.isDark() 83 | ? AppTheme.appTheme.cardBackgroundColor() 84 | : Color(habit.mainColor).withOpacity(0.8), 85 | actions: [ 86 | IconButton( 87 | iconSize: 33, 88 | padding: EdgeInsets.all(16), 89 | icon: SvgPicture.asset( 90 | 'assets/images/bianji.svg', 91 | color: Colors.white, 92 | ), 93 | onPressed: () async { 94 | await Navigator.of(context) 95 | .push(CupertinoPageRoute(builder: (context) { 96 | return HabitEditPage( 97 | isModify: true, 98 | habit: habit, 99 | ); 100 | })); 101 | }, 102 | ), 103 | ], 104 | title: Row( 105 | mainAxisAlignment: MainAxisAlignment.center, 106 | children: [ 107 | Container( 108 | alignment: Alignment.center, 109 | padding: EdgeInsets.all(2), 110 | width: 34, 111 | height: 34, 112 | decoration: BoxDecoration( 113 | shape: BoxShape.circle, 114 | border: Border.all(color: Colors.white, width: 1)), 115 | child: Image.asset(habit.iconPath), 116 | ), 117 | SizedBox( 118 | width: 6, 119 | ), 120 | Text( 121 | habit.name, 122 | style: AppTheme.appTheme.textStyle( 123 | textColor: Colors.white, 124 | fontSize: 18, 125 | fontWeight: FontWeight.w600), 126 | ) 127 | ], 128 | ), 129 | ), 130 | backgroundColor: AppTheme.appTheme.containerBackgroundColor(), 131 | body: CustomScrollView( 132 | controller: _controller, 133 | physics: BouncingScrollPhysics(), 134 | slivers: [ 135 | SliverList( 136 | delegate: SliverChildListDelegate([ 137 | HabitBaseInfoView( 138 | habit: habit, 139 | animationController: _animationController, 140 | ), 141 | HabitCompleteRateView( 142 | habit: habit, 143 | animationController: _animationController, 144 | ), 145 | HabitMonthInfoView( 146 | habit: habit, 147 | animationController: _animationController, 148 | ), 149 | HabitCheckInfoView( 150 | habit: habit, 151 | animationController: _animationController, 152 | ), 153 | HabitUtil.containAllDay(habit) 154 | ? HabitStreakInfoView( 155 | habit: habit, 156 | animationController: _animationController, 157 | ) 158 | : SizedBox(), 159 | HabitRecentRecordsView( 160 | habit: habit, 161 | ), 162 | Container( 163 | height: 100, 164 | ) 165 | ]), 166 | ), 167 | ], 168 | ), 169 | ); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /lib/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:timefly/app_theme.dart'; 3 | import 'package:timefly/habit_progress/habit_progress_screen.dart'; 4 | import 'package:timefly/mine/mine_screen.dart'; 5 | import 'package:timefly/one_day/one_day_screen.dart'; 6 | import 'package:timefly/widget/appbar/fluid_nav_bar.dart'; 7 | 8 | import 'all_habits/all_habits_screen_2.dart'; 9 | class HomeScreen extends StatefulWidget { 10 | @override 11 | State createState() { 12 | return _HomeScreenState(); 13 | } 14 | } 15 | 16 | class _HomeScreenState extends State with TickerProviderStateMixin { 17 | Widget _child; 18 | 19 | @override 20 | void initState() { 21 | _child = OneDayScreen(); 22 | super.initState(); 23 | } 24 | 25 | @override 26 | void dispose() { 27 | super.dispose(); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return Scaffold( 33 | extendBody: true, 34 | backgroundColor: AppTheme.appTheme.containerBackgroundColor(), 35 | body: _child, 36 | bottomNavigationBar: FluidNavBar( 37 | onChange: _handleNavigationChange, 38 | ), 39 | ); 40 | } 41 | 42 | void _handleNavigationChange(int index) { 43 | setState(() { 44 | switch (index) { 45 | case 0: 46 | _child = OneDayScreen(); 47 | break; 48 | case 1: 49 | _child = AllHabitScreen(); 50 | break; 51 | case 2: 52 | _child = HabitProgressScreen(); 53 | break; 54 | case 3: 55 | _child = MineScreen(); 56 | break; 57 | } 58 | _child = AnimatedSwitcher( 59 | switchInCurve: Curves.easeOut, 60 | switchOutCurve: Curves.easeIn, 61 | duration: Duration(milliseconds: 500), 62 | child: _child, 63 | ); 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:timefly/app_theme.dart'; 6 | import 'package:timefly/blocs/habit/habit_bloc.dart'; 7 | import 'package:timefly/blocs/habit/habit_event.dart'; 8 | import 'package:timefly/blocs/theme/theme_bloc.dart'; 9 | import 'package:timefly/blocs/theme/theme_event.dart'; 10 | import 'package:timefly/blocs/theme/theme_state.dart'; 11 | import 'package:timefly/home_screen.dart'; 12 | import 'package:timefly/notification/notification_plugin.dart'; 13 | import 'package:timefly/utils/date_util.dart'; 14 | 15 | import 'blocs/bloc_observer.dart'; 16 | import 'models/user.dart'; 17 | 18 | void main() async { 19 | Bloc.observer = SimpleBlocObserver(); 20 | WidgetsFlutterBinding.ensureInitialized(); 21 | NotificationPlugin.ensureInitialized(); 22 | await SessionUtils.sharedInstance().init(); 23 | await SystemChrome.setPreferredOrientations([ 24 | DeviceOrientation.portraitUp, 25 | DeviceOrientation.portraitDown 26 | ]).then((_) => runApp(MyApp())); 27 | } 28 | 29 | class MyApp extends StatelessWidget { 30 | // This widget is the root of your application. 31 | @override 32 | Widget build(BuildContext context) { 33 | return BlocProvider( 34 | create: (context) => ThemeBloc()..add(ThemeLoadEvnet()), 35 | child: BlocProvider( 36 | create: (context) => HabitsBloc()..add(HabitsLoad()), 37 | child: BlocBuilder( 38 | builder: (context, themeState) { 39 | Future.delayed( 40 | Duration(milliseconds: DateUtil.millisecondsUntilTomorrow()), 41 | () { 42 | BlocProvider.of(context).add(HabitsLoad()); 43 | }); 44 | SessionUtils.sharedInstance() 45 | .setBloc(BlocProvider.of(context)); 46 | return MaterialApp( 47 | title: 'Checkio', 48 | debugShowCheckedModeBanner: false, 49 | theme: AppTheme.appTheme 50 | .themeData() 51 | .copyWith(platform: TargetPlatform.iOS), 52 | home: HomeScreen(), 53 | ); 54 | }, 55 | ), 56 | ), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/mine/mine_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:flutter_svg/svg.dart'; 5 | import 'package:timefly/app_theme.dart'; 6 | import 'package:timefly/blocs/theme/theme_bloc.dart'; 7 | import 'package:timefly/blocs/theme/theme_state.dart'; 8 | import 'package:timefly/mine/mine_screen_views.dart'; 9 | import 'package:timefly/mine/settings_screen.dart'; 10 | import 'package:timefly/utils/system_util.dart'; 11 | 12 | class MineScreen extends StatefulWidget { 13 | @override 14 | _MineScreenState createState() => _MineScreenState(); 15 | } 16 | 17 | class _MineScreenState extends State { 18 | @override 19 | Widget build(BuildContext context) { 20 | return BlocBuilder( 21 | builder: (context, state) { 22 | SystemUtil.changeStateBarMode( 23 | AppTheme.appTheme.isDark() ? Brightness.light : Brightness.dark); 24 | return Stack( 25 | children: [ 26 | ListView(physics: ClampingScrollPhysics(), children: [ 27 | UserInfoView( 28 | callback: () { 29 | setState(() {}); 30 | }, 31 | ), 32 | HabitsTotalView(), 33 | UserProView(), 34 | EnterView(), 35 | SizedBox( 36 | height: 100, 37 | ) 38 | ]), 39 | GestureDetector( 40 | onTap: () async { 41 | await Navigator.of(context) 42 | .push(CupertinoPageRoute(builder: (context) { 43 | return SettingsScreen(); 44 | })); 45 | setState(() {}); 46 | }, 47 | child: Container( 48 | alignment: Alignment.centerRight, 49 | margin: EdgeInsets.only( 50 | top: MediaQuery.of(context).padding.top + 26), 51 | height: 45, 52 | child: Container( 53 | alignment: Alignment.center, 54 | width: 90, 55 | decoration: BoxDecoration( 56 | shape: BoxShape.rectangle, 57 | borderRadius: BorderRadius.only( 58 | topLeft: Radius.circular(26), 59 | bottomLeft: Radius.circular(26)), 60 | color: AppTheme.appTheme.cardBackgroundColor(), 61 | boxShadow: AppTheme.appTheme.containerBoxShadow()), 62 | child: SvgPicture.asset( 63 | 'assets/images/icon_jiaohuan.svg', 64 | width: 25, 65 | height: 25, 66 | color: AppTheme.appTheme.normalColor().withOpacity(0.8), 67 | ), 68 | ), 69 | ), 70 | ) 71 | ], 72 | ); 73 | }, 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/models/complete_time.dart: -------------------------------------------------------------------------------- 1 | class CompleteTime { 2 | ///-1 全部 3 | ///0 任意 4 | ///1 早上 5 | ///2 上午 6 | ///3 中午 7 | ///4 下午 8 | ///5 晚上 9 | final int time; 10 | bool isSelect = false; 11 | 12 | CompleteTime(this.time, {this.isSelect = false}); 13 | 14 | static List getCompleteTimes(int selectIndex) { 15 | List completeTimes = []; 16 | for (int i = 0; i <= 5; i++) { 17 | completeTimes.add(CompleteTime(i, isSelect: i == selectIndex)); 18 | } 19 | return completeTimes; 20 | } 21 | 22 | static String getTime(int time) { 23 | String timeString = '任意'; 24 | switch (time) { 25 | case -1: 26 | timeString = '全部'; 27 | break; 28 | case 1: 29 | timeString = '早上'; 30 | break; 31 | case 2: 32 | timeString = '上午'; 33 | break; 34 | case 3: 35 | timeString = '中午'; 36 | break; 37 | case 4: 38 | timeString = '下午'; 39 | break; 40 | case 5: 41 | timeString = '晚上'; 42 | break; 43 | } 44 | return timeString; 45 | } 46 | } 47 | 48 | class CompleteDay { 49 | final int day; 50 | bool isSelect = false; 51 | 52 | CompleteDay(this.day, {this.isSelect = false}); 53 | 54 | static List getCompleteDays() { 55 | List days = []; 56 | for (int i = 1; i <= 7; i++) { 57 | days.add(CompleteDay(i, isSelect: true)); 58 | } 59 | 60 | return days; 61 | } 62 | 63 | static String getDay(int day) { 64 | String dayString = ''; 65 | switch (day) { 66 | case 1: 67 | dayString = '周一'; 68 | break; 69 | case 2: 70 | dayString = '周二'; 71 | break; 72 | case 3: 73 | dayString = '周三'; 74 | break; 75 | case 4: 76 | dayString = '周四'; 77 | break; 78 | case 5: 79 | dayString = '周五'; 80 | break; 81 | case 6: 82 | dayString = '周六'; 83 | break; 84 | case 7: 85 | dayString = '周日'; 86 | break; 87 | } 88 | return dayString; 89 | } 90 | 91 | static String getEnDay(int day) { 92 | String dayString = 'MO'; 93 | switch (day) { 94 | case 1: 95 | dayString = 'MO'; 96 | break; 97 | case 2: 98 | dayString = 'TU'; 99 | break; 100 | case 3: 101 | dayString = 'WE'; 102 | break; 103 | case 4: 104 | dayString = 'TH'; 105 | break; 106 | case 5: 107 | dayString = 'FR'; 108 | break; 109 | case 6: 110 | dayString = 'SA'; 111 | break; 112 | case 7: 113 | dayString = 'SU'; 114 | break; 115 | } 116 | return dayString; 117 | } 118 | 119 | static String getSimpleDay(int day) { 120 | String dayString = ''; 121 | switch (day) { 122 | case 1: 123 | dayString = '一'; 124 | break; 125 | case 2: 126 | dayString = '二'; 127 | break; 128 | case 3: 129 | dayString = '三'; 130 | break; 131 | case 4: 132 | dayString = '四'; 133 | break; 134 | case 5: 135 | dayString = '五'; 136 | break; 137 | case 6: 138 | dayString = '六'; 139 | break; 140 | case 7: 141 | dayString = '日'; 142 | break; 143 | } 144 | return dayString; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /lib/models/habit.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:equatable/equatable.dart'; 3 | 4 | /// 5 | /// Code generated by jsonToDartModel https://ashamp.github.io/jsonToDartModel/ 6 | /// 7 | // ignore: must_be_immutable 8 | class HabitRecord extends Equatable { 9 | final String habitId; 10 | final int time; 11 | final String content; 12 | 13 | HabitRecord({this.time, this.content, this.habitId}); 14 | 15 | @override 16 | String toString() { 17 | return 'HabitRecord{time: $time, content: $content, habitId: $habitId}'; 18 | } 19 | 20 | static HabitRecord fromJson(Map json) { 21 | return HabitRecord( 22 | habitId: json['habitId']?.toString(), 23 | time: json["time"]?.toInt(), 24 | content: json["content"]?.toString()); 25 | } 26 | 27 | Map toJson() { 28 | final Map data = Map(); 29 | data['habitId'] = habitId; 30 | data["time"] = time; 31 | data["content"] = content; 32 | return data; 33 | } 34 | 35 | @override 36 | List get props => [habitId, time, content]; 37 | 38 | HabitRecord copyWith( 39 | {String habitId, 40 | int time, 41 | String content, 42 | String userId, 43 | String objectId}) { 44 | return HabitRecord( 45 | habitId: habitId ?? this.habitId, 46 | time: time ?? this.time, 47 | content: content ?? this.content); 48 | } 49 | } 50 | 51 | // ignore: must_be_immutable 52 | class Habit extends Equatable { 53 | ///唯一id uuid v4 54 | final String id; 55 | final String name; 56 | final String iconPath; 57 | final int mainColor; 58 | final String mark; 59 | 60 | ///提醒时间 每天 10: 20,eg 61 | ///转化为 json String 存储 ["10:20","11:50"] 62 | final List remindTimes; 63 | 64 | ///完成时间 65 | /// 0 任意 1 早上 2 上午 3 中午 4 下午 5 晚上 66 | final int completeTime; 67 | 68 | ///按周时 完成的day 69 | /// 1-7,周一 -- 周日 70 | final List completeDays; 71 | 72 | ///周期 73 | /// 0 按天 74 | /// 1 按周 75 | /// 2 按月 76 | final int period; 77 | 78 | ///创建时间 79 | final int createTime; 80 | 81 | ///修改时间 82 | final int modifyTime; 83 | 84 | ///是否完成 85 | final bool completed; 86 | 87 | ///次数 88 | final int doNum; 89 | 90 | final List records; 91 | 92 | Habit( 93 | {this.id, 94 | this.name, 95 | this.iconPath, 96 | this.mainColor, 97 | this.mark, 98 | this.remindTimes, 99 | this.completeDays, 100 | this.completeTime, 101 | this.period, 102 | this.createTime, 103 | this.modifyTime, 104 | this.completed, 105 | this.doNum, 106 | this.records}); 107 | 108 | @override 109 | String toString() { 110 | return 'Habit{id: $id , name: $name, iconPath: $iconPath, mainColor: ' 111 | '$mainColor, mark: $mark, remindTimes: $remindTimes, completeTime:' 112 | ' $completeTime, completeDays: $completeDays, period: $period, ' 113 | 'createTime: $createTime, modifyTime: $modifyTime, completed: $completed,' 114 | ' doNum: $doNum, records: $records}'; 115 | } 116 | 117 | static Habit fromJson(Map json, 118 | {List records}) { 119 | var remindTimes = List(); 120 | if (json["remindTimes"] != null) { 121 | var timesJson = jsonDecode(json["remindTimes"]); 122 | timesJson.forEach((json) { 123 | remindTimes.add(json.toString()); 124 | }); 125 | } 126 | var completeDays = List(); 127 | if (json["completeDays"] != null) { 128 | var daysJson = jsonDecode(json["completeDays"]); 129 | daysJson.forEach((json) { 130 | completeDays.add(json.toInt()); 131 | }); 132 | } 133 | 134 | return Habit( 135 | id: json["id"]?.toString(), 136 | name: json["name"]?.toString(), 137 | iconPath: json["iconPath"]?.toString(), 138 | mainColor: json["mainColor"]?.toInt(), 139 | mark: json["mark"]?.toString(), 140 | remindTimes: remindTimes, 141 | completeDays: completeDays, 142 | completeTime: json['completeTime']?.toInt(), 143 | period: json["period"]?.toInt(), 144 | createTime: json["createTime"]?.toInt(), 145 | modifyTime: json["modifyTime"]?.toInt(), 146 | completed: json["completed"]?.toInt() == 1 ? true : false, 147 | doNum: json["doNum"]?.toInt(), 148 | records: records); 149 | } 150 | 151 | Map toJson() { 152 | final Map data = Map(); 153 | data["id"] = id; 154 | data["name"] = name; 155 | data["iconPath"] = iconPath; 156 | data["mainColor"] = mainColor; 157 | data["mark"] = mark; 158 | //转化为String 存储 159 | if (remindTimes != null) { 160 | var times = remindTimes; 161 | var temp = List(); 162 | times.forEach((time) { 163 | temp.add(time); 164 | }); 165 | data["remindTimes"] = jsonEncode(temp); 166 | } 167 | if (completeDays != null) { 168 | var days = completeDays; 169 | var temp = List(); 170 | days.forEach((day) { 171 | temp.add(day); 172 | }); 173 | data["completeDays"] = jsonEncode(temp); 174 | } 175 | 176 | data['completeTime'] = completeTime; 177 | data["period"] = period; 178 | data["createTime"] = createTime; 179 | data["modifyTime"] = modifyTime; 180 | data["completed"] = completed ? 1 : 0; 181 | data["doNum"] = doNum; 182 | return data; 183 | } 184 | 185 | static Habit createHabit(String key) { 186 | return Habit( 187 | id: 'id__$key', 188 | name: 'name__$key', 189 | iconPath: 'assets/images/tab_1.png', 190 | mainColor: 122, 191 | mark: 'mark__$key', 192 | remindTimes: ['time1', 'time2'], 193 | period: 1, 194 | createTime: 1111, 195 | modifyTime: 0, 196 | completed: false, 197 | doNum: 0, 198 | ); 199 | } 200 | 201 | @override 202 | List get props => [ 203 | this.id, 204 | this.name, 205 | this.iconPath, 206 | this.mainColor, 207 | this.mark, 208 | this.remindTimes, 209 | this.completeDays, 210 | this.completeTime, 211 | this.period, 212 | this.createTime, 213 | this.modifyTime, 214 | this.completed, 215 | this.doNum, 216 | this.records, 217 | ]; 218 | 219 | Habit copyWith( 220 | {String objectId, 221 | String id, 222 | String name, 223 | String iconPath, 224 | int mainColor, 225 | String mark, 226 | List remindTimes, 227 | List completeDays, 228 | int completeTime, 229 | int period, 230 | int createTime, 231 | int modifyTime, 232 | bool completed, 233 | int doNum, 234 | List records, 235 | String userId}) { 236 | return Habit( 237 | id: id ?? this.id, 238 | name: name ?? this.name, 239 | iconPath: iconPath ?? this.iconPath, 240 | mainColor: mainColor ?? this.mainColor, 241 | mark: mark ?? this.mark, 242 | remindTimes: remindTimes ?? this.remindTimes, 243 | completeDays: completeDays ?? this.completeDays, 244 | completeTime: completeTime ?? this.completeTime, 245 | period: period ?? this.period, 246 | createTime: createTime ?? this.createTime, 247 | modifyTime: modifyTime ?? this.modifyTime, 248 | completed: completed ?? this.completed, 249 | doNum: doNum ?? this.doNum, 250 | records: records ?? this.records); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /lib/models/habit_color.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HabitColor { 4 | final Color color; 5 | bool isSelect = false; 6 | 7 | HabitColor(this.color, {this.isSelect = false}); 8 | 9 | static List getBackgroundColors() { 10 | List backgroundColors = []; 11 | 12 | backgroundColors.add(HabitColor(Colors.deepPurpleAccent, isSelect: true)); 13 | backgroundColors.add(HabitColor(Colors.purple)); 14 | backgroundColors.add(HabitColor(Colors.lightBlue)); 15 | backgroundColors.add(HabitColor(Colors.red)); 16 | backgroundColors.add(HabitColor(Colors.blueAccent)); 17 | backgroundColors.add(HabitColor(Colors.pink)); 18 | backgroundColors.add(HabitColor(Colors.deepOrange)); 19 | return backgroundColors; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/models/habit_color2.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HabitColor { 4 | ///作为唯一key存入数据库 5 | final Color startColor; 6 | final Color endColor; 7 | bool isSelect; 8 | 9 | HabitColor(this.startColor, this.endColor, {this.isSelect = false}); 10 | 11 | bool equals(HabitColor color) { 12 | return startColor == color.startColor; 13 | } 14 | } 15 | 16 | class HabitColorsUtil { 17 | static HabitColorsUtil _instance = HabitColorsUtil(); 18 | 19 | static HabitColorsUtil instance() => _instance; 20 | List _habitColors = []; 21 | 22 | void initHabitColors() { 23 | _habitColors 24 | .add(HabitColor(Color(0xff738AE6), Color(0xff5C5EDD), isSelect: true)); 25 | _habitColors.add(HabitColor(Color(0xffF45C43), Color(0xffEB3349))); 26 | _habitColors.add(HabitColor(Color(0xffF09819), Color(0xffFF512F))); 27 | _habitColors.add(HabitColor(Color(0xffAA076B), Color(0xff61045F))); 28 | _habitColors.add(HabitColor(Color(0xffFF512F), Color(0xffDD2476))); 29 | _habitColors.add(HabitColor(Color(0xffDA22FF), Color(0xff9733EE))); 30 | _habitColors.add(HabitColor(Color(0xff00CDAC), Color(0xff02AAB0))); 31 | _habitColors.add(HabitColor(Color(0xffe52d27), Color(0xffb31217))); 32 | _habitColors.add(HabitColor(Color(0xff2b5876), Color(0xff4e4376))); 33 | _habitColors.add(HabitColor(Color(0xffF9D423), Color(0xffe65c00))); 34 | 35 | _habitColors.add(HabitColor(Color(0xff6dd5ed), Color(0xff2193b0))); 36 | _habitColors.add(HabitColor(Color(0xffcc2b5e), Color(0xff753a88))); 37 | _habitColors.add(HabitColor(Color(0xff1488CC), Color(0xff2B32B2))); 38 | _habitColors.add(HabitColor(Color(0xffB79891), Color(0xff94716B))); 39 | _habitColors.add(HabitColor(Color(0xff536976), Color(0xff292E49))); 40 | _habitColors.add(HabitColor(Color(0xffffe259), Color(0xffffa751))); 41 | _habitColors.add(HabitColor(Color(0xffACBB78), Color(0xff799F0C))); 42 | _habitColors.add(HabitColor(Color(0xffffdde1), Color(0xffee9ca7))); 43 | _habitColors.add(HabitColor(Color(0xff6dd5ed), Color(0xff2193b0))); 44 | _habitColors.add(HabitColor(Color(0xff2C5364), Color(0xff203A43))); 45 | 46 | _habitColors.add(HabitColor(Color(0xff4286f4), Color(0xff373B44))); 47 | _habitColors.add(HabitColor(Color(0xffFF0099), Color(0xff493240))); 48 | _habitColors.add(HabitColor(Color(0xff8E2DE2), Color(0xff4A00E0))); 49 | _habitColors.add(HabitColor(Color(0xfff953c6), Color(0xffb91d73))); 50 | _habitColors.add(HabitColor(Color(0xffc31432), Color(0xff240b36))); 51 | _habitColors.add(HabitColor(Color(0xfff5af19), Color(0xfff12711))); 52 | _habitColors.add(HabitColor(Color(0xffeaafc8), Color(0xff654ea3))); 53 | _habitColors.add(HabitColor(Color(0xffFF416C), Color(0xffFF4B2B))); 54 | _habitColors.add(HabitColor(Color(0xff00B4DB), Color(0xff0083B0))); 55 | _habitColors.add(HabitColor(Color(0xffad5389), Color(0xff3c1053))); 56 | } 57 | 58 | List getHabitColors() { 59 | if (_habitColors.length == 0) { 60 | initHabitColors(); 61 | } 62 | return _habitColors; 63 | } 64 | 65 | HabitColor getColor(Color startColor) { 66 | if (_habitColors.length == 0) { 67 | initHabitColors(); 68 | } 69 | return _habitColors.firstWhere((color) => color.startColor == startColor); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/models/habit_icon.dart: -------------------------------------------------------------------------------- 1 | class HabitIcon { 2 | final String icon; 3 | bool isSelect = false; 4 | 5 | HabitIcon(this.icon, {this.isSelect = false}); 6 | 7 | static List getIcons() { 8 | List icons = []; 9 | 10 | icons.add(HabitIcon('assets/images/aquarium-水族馆.png', isSelect: true)); 11 | icons.add(HabitIcon('assets/images/bumblebee-熊峰.png')); 12 | icons.add(HabitIcon('assets/images/butterfly-蝴蝶.png')); 13 | icons.add(HabitIcon('assets/images/cat-footprint-猫抓.png')); 14 | icons.add(HabitIcon('assets/images/cute-hamster-可爱仓鼠.png')); 15 | icons.add(HabitIcon('assets/images/dinosaur-egg-龙宝宝.png')); 16 | icons.add(HabitIcon('assets/images/dog-狗.png')); 17 | icons.add(HabitIcon('assets/images/dove-鸽子.png')); 18 | icons.add(HabitIcon('assets/images/flamingo-火烈鸟.png')); 19 | icons.add(HabitIcon('assets/images/parrot-鹦鹉.png')); 20 | icons.add(HabitIcon('assets/images/swan-天鹅.png')); 21 | 22 | icons.add(HabitIcon('assets/images/badminton-player-羽毛球.png')); 23 | icons.add(HabitIcon('assets/images/basketball-篮球.png')); 24 | icons.add(HabitIcon('assets/images/cycling-自行车.png')); 25 | icons.add(HabitIcon('assets/images/exercise-运动.png')); 26 | icons.add(HabitIcon('assets/images/fishing-钓鱼.png')); 27 | icons.add(HabitIcon('assets/images/jump-rope-跳绳.png')); 28 | icons.add(HabitIcon('assets/images/pilates-普拉提.png')); 29 | icons.add(HabitIcon('assets/images/ping-pong-乒乓球.png')); 30 | icons.add(HabitIcon('assets/images/skateboard-滑板.png')); 31 | icons.add(HabitIcon('assets/images/tennis-racquet-网球.png')); 32 | icons.add(HabitIcon('assets/images/treadmill-跑步机.png')); 33 | 34 | return icons; 35 | } 36 | } -------------------------------------------------------------------------------- /lib/models/habit_list_model.dart: -------------------------------------------------------------------------------- 1 | ///one day screen habit datas 2 | class OnDayHabitListData { 3 | static const int typeHeader = 0; 4 | static const int typeTip = 1; 5 | static const int typeTitle = 2; 6 | static const int typeHabits = 3; 7 | static const int typeRate = 4; 8 | 9 | final int type; 10 | final dynamic value; 11 | 12 | const OnDayHabitListData({this.type, this.value}); 13 | } 14 | -------------------------------------------------------------------------------- /lib/models/habit_peroid.dart: -------------------------------------------------------------------------------- 1 | class HabitPeriod { 2 | static const int day = 0; 3 | static const int week = 1; 4 | static const int month = 2; 5 | 6 | ///0 按天 7 | ///1 按周 8 | ///2 按月 9 | final int period; 10 | bool isSelect = false; 11 | 12 | HabitPeriod(this.period, {this.isSelect = false}); 13 | 14 | static List getHabitPeriods(int period) { 15 | List periods = []; 16 | for (int i = 0; i <= 2; i++) { 17 | periods.add(HabitPeriod(i, isSelect: i == period)); 18 | } 19 | return periods; 20 | } 21 | 22 | static String getPeriod(int peroid) { 23 | String periodString = '天'; 24 | switch (peroid) { 25 | case day: 26 | periodString = '天'; 27 | break; 28 | case week: 29 | periodString = '周'; 30 | break; 31 | case month: 32 | periodString = '月'; 33 | break; 34 | } 35 | return periodString; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/models/user.dart: -------------------------------------------------------------------------------- 1 | import 'package:timefly/blocs/habit/habit_bloc.dart'; 2 | import 'package:timefly/blocs/habit/habit_event.dart'; 3 | import 'package:timefly/db/database_provider.dart'; 4 | 5 | class User { 6 | final String id; 7 | final String username; 8 | final String phone; 9 | 10 | User(this.id, this.username, this.phone); 11 | 12 | static User fromJson(Map json) { 13 | return User(json['id']?.toString(), json["username"]?.toString(), 14 | json["phone"]?.toString()); 15 | } 16 | 17 | Map toJson() { 18 | final Map data = Map(); 19 | data['id'] = id; 20 | data["username"] = username; 21 | data["phone"] = phone; 22 | return data; 23 | } 24 | 25 | User copyWith({String id, String username, String phone}) { 26 | return User(id ?? this.id, username ?? this.username, phone ?? this.phone); 27 | } 28 | } 29 | 30 | class SessionUtils { 31 | SessionUtils._(); 32 | 33 | factory SessionUtils() => sharedInstance(); 34 | 35 | static SessionUtils sharedInstance() { 36 | return _instance; 37 | } 38 | 39 | static SessionUtils _instance = SessionUtils._(); 40 | 41 | User currentUser; 42 | HabitsBloc habitsBloc; 43 | 44 | init() async { 45 | currentUser = await DatabaseProvider.db.getCurrentUser(); 46 | print('init user -- ${currentUser?.toJson()}'); 47 | } 48 | 49 | void setBloc(HabitsBloc habitsBloc) { 50 | this.habitsBloc = habitsBloc; 51 | } 52 | 53 | void login(User user) async { 54 | if (currentUser != null) { 55 | await DatabaseProvider.db.deleteUser(); 56 | } 57 | currentUser = user; 58 | await DatabaseProvider.db.saveUser(user); 59 | habitsBloc.add(HabitsLoad()); 60 | } 61 | 62 | void logout() async { 63 | currentUser = null; 64 | await DatabaseProvider.db.deleteUser(); 65 | habitsBloc.add(HabitsLoad()); 66 | } 67 | 68 | void updateName(String name) async { 69 | currentUser = currentUser.copyWith(username: name); 70 | await DatabaseProvider.db.updateUser(currentUser); 71 | } 72 | 73 | bool isLogin() { 74 | return currentUser != null; 75 | } 76 | 77 | String getUserId() { 78 | return currentUser?.id; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/notification/notification_plugin.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter_local_notifications/flutter_local_notifications.dart'; 4 | import 'package:timezone/data/latest.dart' as tz; 5 | import 'package:timezone/timezone.dart' as tz; 6 | 7 | class NotificationPlugin { 8 | static ensureInitialized() { 9 | if (_instance == null) { 10 | _getInstance(); 11 | } 12 | } 13 | 14 | factory NotificationPlugin.getInstance() => _getInstance(); 15 | 16 | static NotificationPlugin _instance; 17 | 18 | static _getInstance() { 19 | if (_instance == null) { 20 | _instance = NotificationPlugin._(); 21 | } 22 | return _instance; 23 | } 24 | 25 | FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; 26 | 27 | NotificationPlugin._() { 28 | init(); 29 | } 30 | 31 | void init() async { 32 | flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); 33 | if (Platform.isIOS) { 34 | _requestPermission(); 35 | } 36 | initializePlatformSpecifics(); 37 | } 38 | 39 | void _requestPermission() { 40 | flutterLocalNotificationsPlugin 41 | .resolvePlatformSpecificImplementation< 42 | IOSFlutterLocalNotificationsPlugin>() 43 | .requestPermissions(alert: true, sound: true, badge: true); 44 | } 45 | 46 | void initializePlatformSpecifics() async { 47 | AndroidInitializationSettings androidInitializationSettings = 48 | AndroidInitializationSettings('notification'); 49 | IOSInitializationSettings iosInitializationSettings = 50 | IOSInitializationSettings(); 51 | final InitializationSettings initializationSettings = 52 | InitializationSettings( 53 | android: androidInitializationSettings, 54 | iOS: iosInitializationSettings); 55 | await flutterLocalNotificationsPlugin.initialize(initializationSettings, 56 | onSelectNotification: (payload) async {}); 57 | } 58 | 59 | Future scheduleNotification() async { 60 | var dateTime = tz.TZDateTime.now(tz.UTC).add(Duration(seconds: 5)); 61 | var androidChannelSpecifics = AndroidNotificationDetails( 62 | 'channel_id', 'channel_name', 'channel_desc', 63 | importance: Importance.max, 64 | priority: Priority.high, 65 | timeoutAfter: 5000); 66 | var platformChannelSpecifics = 67 | NotificationDetails(android: androidChannelSpecifics); 68 | await flutterLocalNotificationsPlugin.zonedSchedule( 69 | 1, 'title', 'body', dateTime, platformChannelSpecifics, 70 | uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, 71 | androidAllowWhileIdle: true); 72 | return Future.value(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/one_day/lol_words.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | class LoLWords { 4 | final String word; 5 | final String wordEnglish; 6 | 7 | LoLWords(this.word, this.wordEnglish); 8 | } 9 | 10 | class LoLWordsFactory { 11 | static List words = [ 12 | LoLWords('且随疾风前行,身后亦须留心。', 'Follow the wind, but watch your back.'), 13 | LoLWords('哼,一个能打的都没有。', 'Who wants a piece of the champ?') 14 | ]; 15 | 16 | static LoLWords randomWord() { 17 | return words[Random().nextInt(words.length)]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/one_day/one_day_normal_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:timefly/add_habit/habit_edit_page.dart'; 4 | import 'package:timefly/login/login_page.dart'; 5 | import 'package:timefly/models/user.dart'; 6 | import 'package:timefly/one_day/lol_words.dart'; 7 | import 'package:timefly/utils/date_util.dart'; 8 | 9 | import '../app_theme.dart'; 10 | 11 | ///当前时间提示 and 美丽的句子 12 | class TimeAndWordView extends StatelessWidget { 13 | final AnimationController animationController; 14 | final Animation animation; 15 | 16 | const TimeAndWordView({Key key, this.animationController, this.animation}) 17 | : super(key: key); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | LoLWords words = LoLWordsFactory.randomWord(); 22 | return AnimatedBuilder( 23 | animation: animationController, 24 | builder: (context, child) { 25 | return SlideTransition( 26 | position: animation, 27 | child: Padding( 28 | padding: EdgeInsets.only( 29 | left: 16, 30 | right: 50, 31 | top: MediaQuery.of(context).padding.top + 26, 32 | bottom: 20), 33 | child: Column( 34 | crossAxisAlignment: CrossAxisAlignment.start, 35 | children: [ 36 | GestureDetector( 37 | onTap: () async {}, 38 | child: Text( 39 | '${DateUtil.getNowTimeString()}好,', 40 | style: AppTheme.appTheme 41 | .headline1(fontWeight: FontWeight.bold, fontSize: 23), 42 | ), 43 | ), 44 | Text( 45 | words.word, 46 | style: AppTheme.appTheme 47 | .headline1(fontSize: 16, fontWeight: FontWeight.normal), 48 | ), 49 | Text( 50 | words.wordEnglish, 51 | style: AppTheme.appTheme 52 | .numHeadline1(fontSize: 16, fontWeight: FontWeight.normal) 53 | .copyWith(fontFamily: 'Montserrat'), 54 | ) 55 | ], 56 | ), 57 | ), 58 | ); 59 | }, 60 | ); 61 | } 62 | } 63 | 64 | class OneDayTipsView extends StatelessWidget { 65 | final AnimationController animationController; 66 | final Animation animation; 67 | final int habitLength; 68 | 69 | const OneDayTipsView( 70 | {Key key, this.animationController, this.animation, this.habitLength}) 71 | : super(key: key); 72 | 73 | @override 74 | Widget build(BuildContext context) { 75 | return AnimatedBuilder( 76 | animation: animationController, 77 | builder: (context, child) { 78 | return SlideTransition( 79 | position: animation, 80 | child: GestureDetector( 81 | onTap: () async { 82 | if (!SessionUtils.sharedInstance().isLogin()) { 83 | Navigator.of(context) 84 | .push(CupertinoPageRoute(builder: (context) { 85 | return LoginPage(); 86 | })); 87 | return; 88 | } 89 | await Navigator.of(context) 90 | .push(CupertinoPageRoute(builder: (context) { 91 | return HabitEditPage( 92 | isModify: false, 93 | habit: null, 94 | ); 95 | })); 96 | }, 97 | child: Padding( 98 | padding: EdgeInsets.only(left: 50), 99 | child: Container( 100 | alignment: Alignment.center, 101 | height: 100, 102 | decoration: BoxDecoration( 103 | boxShadow: AppTheme.appTheme.coloredBoxShadow(), 104 | gradient: AppTheme.appTheme.containerGradient( 105 | begin: Alignment.centerLeft, 106 | end: Alignment.centerRight, 107 | ), 108 | borderRadius: BorderRadius.only( 109 | topLeft: Radius.circular(20), 110 | bottomLeft: Radius.circular(20))), 111 | child: Text( 112 | '${habitLength == 0 ? '点击添加一个习惯吧...' : '今天没有要完成的习惯\n点击新建一个吧'}', 113 | style: AppTheme.appTheme.headline1( 114 | textColor: Colors.white, 115 | fontWeight: FontWeight.normal, 116 | fontSize: 18), 117 | ), 118 | ), 119 | ), 120 | ), 121 | ); 122 | }, 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/one_day/one_day_rate_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:timefly/models/habit.dart'; 3 | import 'package:timefly/models/habit_peroid.dart'; 4 | import 'package:timefly/utils/date_util.dart'; 5 | import 'package:timefly/utils/habit_util.dart'; 6 | import 'package:timefly/utils/pair.dart'; 7 | import 'package:timefly/widget/circle_progress_bar.dart'; 8 | 9 | import '../app_theme.dart'; 10 | 11 | ///周期为天的习惯完成率 12 | class OneDayRateView extends StatelessWidget { 13 | final int period; 14 | final List allHabits; 15 | final Animation animation; 16 | 17 | const OneDayRateView({Key key, this.period, this.allHabits, this.animation}) 18 | : super(key: key); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | List habits = allHabits; 23 | if (period == HabitPeriod.day) { 24 | int weekend = DateTime.now().weekday; 25 | habits = habits 26 | .where((element) => element.completeDays.contains(weekend)) 27 | .toList(); 28 | } 29 | int needCompleteNnm = _needCompleteNum(habits); 30 | int hasDoNum = _hasDoNum(habits); 31 | return SlideTransition( 32 | position: animation, 33 | child: Padding( 34 | padding: EdgeInsets.only(left: 40, bottom: 8, top: 8), 35 | child: Row( 36 | children: [ 37 | Expanded( 38 | child: Container( 39 | padding: EdgeInsets.only(left: 16, right: 16, bottom: 8, top: 8), 40 | decoration: BoxDecoration( 41 | shape: BoxShape.rectangle, 42 | borderRadius: BorderRadius.only( 43 | topLeft: Radius.circular(25), 44 | topRight: Radius.circular(35), 45 | bottomRight: Radius.circular(35), 46 | bottomLeft: Radius.circular(15)), 47 | gradient: AppTheme.appTheme.containerGradient(), 48 | boxShadow: AppTheme.appTheme.coloredBoxShadow()), 49 | child: _tipView(hasDoNum, needCompleteNnm), 50 | )), 51 | SizedBox( 52 | width: 10, 53 | ), 54 | Container( 55 | margin: EdgeInsets.only(right: 16), 56 | padding: EdgeInsets.all(5), 57 | decoration: BoxDecoration( 58 | shape: BoxShape.circle, 59 | color: AppTheme.appTheme.cardBackgroundColor(), 60 | boxShadow: AppTheme.appTheme.containerBoxShadow()), 61 | width: 60, 62 | height: 60, 63 | child: Stack( 64 | alignment: Alignment.center, 65 | children: [ 66 | CircleProgressBar( 67 | backgroundColor: 68 | AppTheme.appTheme.containerBackgroundColor(), 69 | foregroundColor: AppTheme.appTheme.grandientColorEnd(), 70 | value: hasDoNum / needCompleteNnm), 71 | Text( 72 | '${((hasDoNum / needCompleteNnm) * 100).toInt()}%', 73 | style: AppTheme.appTheme.numHeadline1( 74 | fontSize: 14, fontWeight: FontWeight.normal), 75 | ) 76 | ], 77 | ), 78 | ) 79 | ], 80 | ), 81 | ), 82 | ); 83 | } 84 | 85 | Widget _tipView(int hasDoNum, int needCompleteNum) { 86 | String tip = '以‘天’为周期习惯,今天,'; 87 | if (period == HabitPeriod.week) { 88 | tip = '以‘周’为周期习惯,本周,'; 89 | } else if (period == HabitPeriod.month) { 90 | tip = '以‘月’为周期习惯,本月'; 91 | } 92 | return Column( 93 | children: [ 94 | Text( 95 | tip, 96 | style: AppTheme.appTheme.headline1( 97 | textColor: Colors.white70, 98 | fontSize: 15, 99 | ), 100 | ), 101 | Row( 102 | mainAxisAlignment: MainAxisAlignment.center, 103 | crossAxisAlignment: CrossAxisAlignment.center, 104 | children: [ 105 | Text( 106 | '共需完成', 107 | style: AppTheme.appTheme.headline1( 108 | textColor: Colors.white, 109 | fontSize: 14, 110 | fontWeight: FontWeight.normal), 111 | ), 112 | SizedBox( 113 | width: 3, 114 | ), 115 | Text('$needCompleteNum', 116 | style: AppTheme.appTheme.numHeadline1( 117 | fontSize: 22, 118 | textColor: Colors.white, 119 | fontWeight: FontWeight.bold, 120 | )), 121 | SizedBox( 122 | width: 3, 123 | ), 124 | Text('已完成', 125 | style: AppTheme.appTheme.headline1( 126 | textColor: Colors.white, 127 | fontSize: 14, 128 | fontWeight: FontWeight.normal)), 129 | SizedBox( 130 | width: 3, 131 | ), 132 | Text('$hasDoNum', 133 | style: AppTheme.appTheme.numHeadline1( 134 | fontSize: 22, 135 | textColor: Colors.white, 136 | fontWeight: FontWeight.bold, 137 | )), 138 | ], 139 | ) 140 | ], 141 | ); 142 | } 143 | 144 | int _needCompleteNum(List habits) { 145 | int count = 0; 146 | habits.where((element) => element.period == period).forEach((element) { 147 | count += element.doNum; 148 | }); 149 | return count; 150 | } 151 | 152 | int _hasDoNum(List habits) { 153 | int count = 0; 154 | final DateTime _now = DateTime.now(); 155 | DateTime start; 156 | DateTime end; 157 | if (period == HabitPeriod.day) { 158 | start = DateUtil.getDayPeroid(_now, 0); 159 | end = DateUtil.getDayPeroid(_now, 0); 160 | } else if (period == HabitPeriod.week) { 161 | Pair weekSAE = DateUtil.getWeekStartAndEnd(_now, 0); 162 | start = weekSAE.x0; 163 | end = weekSAE.x1; 164 | } else { 165 | Pair monthSAE = DateUtil.getMonthStartAndEnd(_now, 0); 166 | start = monthSAE.x0; 167 | end = monthSAE.x1; 168 | } 169 | habits.where((element) => element.period == period).forEach((element) { 170 | count += HabitUtil.getDoCountOfHabit(element.records, start, end); 171 | }); 172 | return count; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /lib/utils/flash_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flash/flash.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../app_theme.dart'; 5 | 6 | class FlashHelper { 7 | static Future toast(BuildContext context, String message) async { 8 | return showFlash( 9 | context: context, 10 | duration: Duration(milliseconds: 2000), 11 | builder: (context, controller) { 12 | return Flash.bar( 13 | margin: EdgeInsets.only(left: 24, right: 24), 14 | position: FlashPosition.top, 15 | brightness: AppTheme.appTheme.isDark() 16 | ? Brightness.light 17 | : Brightness.dark, 18 | backgroundColor: Colors.transparent, 19 | controller: controller, 20 | child: Container( 21 | alignment: Alignment.center, 22 | padding: EdgeInsets.all(16), 23 | height: 80, 24 | decoration: BoxDecoration( 25 | shape: BoxShape.rectangle, 26 | borderRadius: BorderRadius.all(Radius.circular(16)), 27 | gradient: AppTheme.appTheme.containerGradient(), 28 | boxShadow: AppTheme.appTheme.coloredBoxShadow()), 29 | child: Text( 30 | message, 31 | style: AppTheme.appTheme.headline1( 32 | textColor: Colors.white, 33 | fontWeight: FontWeight.normal, 34 | fontSize: 16), 35 | ), 36 | )); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/utils/hex_color.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HexColor extends Color { 4 | HexColor(final String hexColor) : super(_getColorFromHex(hexColor)); 5 | 6 | static int _getColorFromHex(String hexColor) { 7 | hexColor = hexColor.toUpperCase().replaceAll('#', ''); 8 | if (hexColor.length == 6) { 9 | hexColor = 'FF' + hexColor; 10 | } 11 | return int.parse(hexColor, radix: 16); 12 | } 13 | 14 | static Color darken(Color color, [double amount = .1]) { 15 | assert(amount >= 0 && amount <= 1); 16 | 17 | final hsl = HSLColor.fromColor(color); 18 | final hslDark = hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0)); 19 | 20 | return hslDark.toColor(); 21 | } 22 | } -------------------------------------------------------------------------------- /lib/utils/list_utils.dart: -------------------------------------------------------------------------------- 1 | class ListUtils { 2 | static bool equals(List first, List second) { 3 | if (first.length != second.length) { 4 | return false; 5 | } 6 | bool equals = true; 7 | for (int i = 0; i < first.length; i++) { 8 | if (first[i] != second[i]) { 9 | equals = false; 10 | break; 11 | } 12 | } 13 | return equals; 14 | } 15 | 16 | static bool containsRemindTime(List remindTimes, DateTime time) { 17 | bool contain = false; 18 | if (remindTimes.length == 0) { 19 | return false; 20 | } 21 | for (var value in remindTimes) { 22 | if (value.hour == time.hour && value.minute == time.minute) { 23 | contain = true; 24 | break; 25 | } 26 | } 27 | return contain; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/utils/pair.dart: -------------------------------------------------------------------------------- 1 | class Pair { 2 | final T x0; 3 | final T x1; 4 | 5 | Pair(this.x0, this.x1); 6 | 7 | @override 8 | String toString() { 9 | return '[$x0,$x1]'; 10 | } 11 | } 12 | 13 | class Pair2 { 14 | final S s; 15 | final T t; 16 | 17 | Pair2(this.s, this.t); 18 | 19 | @override 20 | String toString() { 21 | return 'Pair2{s: $s, t: $t}'; 22 | } 23 | } 24 | 25 | ///可变的 26 | class Mutable { 27 | T value; 28 | 29 | Mutable(this.value); 30 | 31 | @override 32 | String toString() { 33 | return 'Mutable{value: $value}'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/utils/path_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui' as ui; 2 | 3 | /// Returns the subset of the input path from start to end 4 | /// `start` and `end` are normalized in the range (0.0, 1.0) 5 | ui.Path extractPartialPath(ui.Path path, double start, double end) { 6 | assert(0.0 <= start && start <= 1.0); 7 | assert(0.0 <= end && end <= 1.0); 8 | assert(start < end); 9 | var result = ui.Path(); 10 | final metrics = path.computeMetrics().toList(); 11 | var totalLength = 0.0; 12 | for (var m in metrics) { 13 | totalLength += m.length; 14 | } 15 | final startPos = start * totalLength; 16 | final endPos = end * totalLength; 17 | var l = 0.0; 18 | for (var m in metrics) { 19 | final localStartPos = (startPos - l).clamp(0.0, m.length); 20 | final localEndPos = (endPos - l).clamp(0.0, m.length); 21 | 22 | if (localStartPos < localEndPos) 23 | result.addPath(m.extractPath(localStartPos, localEndPos), ui.Offset.zero); 24 | l += m.length; 25 | } 26 | 27 | return result; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /lib/utils/system_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:timefly/app_theme.dart'; 6 | 7 | class SystemUtil { 8 | static void changeStateBarMode(Brightness brightness) { 9 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 10 | statusBarColor: Colors.transparent, 11 | statusBarIconBrightness: brightness, 12 | statusBarBrightness: 13 | Platform.isAndroid ? Brightness.dark : Brightness.light, 14 | systemNavigationBarColor: AppTheme.appTheme.cardBackgroundColor(), 15 | systemNavigationBarDividerColor: Colors.grey, 16 | systemNavigationBarIconBrightness: brightness, 17 | )); 18 | } 19 | 20 | static SystemUiOverlayStyle getSystemUiOverlayStyle(Brightness brightness) { 21 | return SystemUiOverlayStyle( 22 | statusBarColor: Colors.transparent, 23 | statusBarIconBrightness: brightness, 24 | statusBarBrightness: 25 | Platform.isAndroid ? Brightness.dark : Brightness.light, 26 | systemNavigationBarColor: AppTheme.appTheme.cardBackgroundColor(), 27 | systemNavigationBarDividerColor: Colors.grey, 28 | systemNavigationBarIconBrightness: brightness, 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/utils/uuid.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. 2 | // Use of this source code is governed by the MIT license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'dart:math'; 6 | 7 | /// A UUID generator, useful for generating unique IDs for your Todos. 8 | /// Shamelessly extracted from the Flutter source code. 9 | /// 10 | /// This will generate unique IDs in the format: 11 | /// 12 | /// f47ac10b-58cc-4372-a567-0e02b2c3d479 13 | /// 14 | /// ### Example 15 | /// 16 | /// final String id = Uuid().generateV4(); 17 | class Uuid { 18 | final Random _random = Random(); 19 | 20 | /// Generate a version 4 (random) uuid. This is a uuid scheme that only uses 21 | /// random numbers as the source of the generated uuid. 22 | String generateV4() { 23 | // Generate xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx / 8-4-4-4-12. 24 | final special = 8 + _random.nextInt(4); 25 | 26 | return '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}-' 27 | '${_bitsDigits(16, 4)}-' 28 | '4${_bitsDigits(12, 3)}-' 29 | '${_printDigits(special, 1)}${_bitsDigits(12, 3)}-' 30 | '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}'; 31 | } 32 | 33 | String _bitsDigits(int bitCount, int digitCount) => 34 | _printDigits(_generateBits(bitCount), digitCount); 35 | 36 | int _generateBits(int bitCount) => _random.nextInt(1 << bitCount); 37 | 38 | String _printDigits(int value, int count) => 39 | value.toRadixString(16).padLeft(count, '0'); 40 | } 41 | -------------------------------------------------------------------------------- /lib/widget/appbar/curves.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:flutter/animation.dart'; 4 | 5 | class CenteredElasticOutCurve extends Curve { 6 | 7 | final double period; 8 | 9 | CenteredElasticOutCurve([this.period = 0.4]); 10 | 11 | @override 12 | double transform(double x) { 13 | // Bascially just a slightly modified version of the built in ElasticOutCurve 14 | return math.pow(2.0, -10.0 * x) * math.sin(x * 2.0 * math.pi / period) + 0.5; 15 | } 16 | } 17 | 18 | class CenteredElasticInCurve extends Curve { 19 | 20 | final double period; 21 | 22 | CenteredElasticInCurve([this.period = 0.4]); 23 | 24 | @override 25 | double transform(double x) { 26 | // Bascially just a slightly modified version of the built in ElasticInCurve 27 | return -math.pow(2.0, 10.0 * (x - 1.0)) * math.sin((x - 1.0) * 2.0 * math.pi / period) + 0.5; 28 | } 29 | } 30 | 31 | class LinearPointCurve extends Curve { 32 | final double pIn; 33 | final double pOut; 34 | 35 | LinearPointCurve(this.pIn, this.pOut); 36 | 37 | @override 38 | double transform(double x) { 39 | // Just a simple bit of linear interpolation math 40 | final lowerScale = pOut / pIn; 41 | final upperScale = (1.0 - pOut) / (1.0 - pIn); 42 | final upperOffset = 1.0 - upperScale; 43 | return x < pIn ? x * lowerScale : x * upperScale + upperOffset; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /lib/widget/appbar/fluid_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/animation.dart'; 3 | import 'package:timefly/app_theme.dart'; 4 | 5 | import './fluid_icon.dart'; 6 | import './curves.dart'; 7 | 8 | typedef void FluidNavBarButtonPressedCallback(); 9 | 10 | class FluidNavBarButton extends StatefulWidget { 11 | static const nominalExtent = const Size(64, 64); 12 | 13 | final FluidFillIconData _iconData; 14 | final bool _selected; 15 | final FluidNavBarButtonPressedCallback _onPressed; 16 | 17 | FluidNavBarButton(FluidFillIconData iconData, bool selected, FluidNavBarButtonPressedCallback onPressed) 18 | : _iconData = iconData, _selected = selected, _onPressed = onPressed; 19 | 20 | @override 21 | State createState() { 22 | return _FluidNavBarButtonState(_iconData, _selected, _onPressed); 23 | } 24 | } 25 | 26 | class _FluidNavBarButtonState extends State with SingleTickerProviderStateMixin { 27 | static const double _activeOffset = 16; 28 | static const double _defaultOffset = 0; 29 | static const double _radius = 25; 30 | 31 | FluidFillIconData _iconData; 32 | bool _selected; 33 | FluidNavBarButtonPressedCallback _onPressed; 34 | 35 | AnimationController _animationController; 36 | Animation _animation; 37 | 38 | _FluidNavBarButtonState(FluidFillIconData iconData, bool selected, FluidNavBarButtonPressedCallback onPressed) 39 | : _iconData = iconData, 40 | _selected = selected, 41 | _onPressed = onPressed; 42 | 43 | @override 44 | void initState() { 45 | _animationController = AnimationController( 46 | duration: const Duration(milliseconds: 1666), 47 | reverseDuration: const Duration(milliseconds: 833), 48 | vsync: this); 49 | _animation = Tween(begin: 0.0, end: 1.0).animate(_animationController) 50 | ..addListener(() { 51 | setState(() { 52 | }); 53 | }); 54 | _startAnimation(); 55 | 56 | super.initState(); 57 | } 58 | 59 | @override 60 | void dispose() { 61 | _animationController.dispose(); 62 | super.dispose(); 63 | } 64 | 65 | @override 66 | Widget build(context) { 67 | const ne = FluidNavBarButton.nominalExtent; 68 | final offsetCurve = _selected ? ElasticOutCurve(0.38) : Curves.easeInQuint; 69 | final scaleCurve = _selected ? CenteredElasticOutCurve(0.6) : CenteredElasticInCurve(0.6); 70 | 71 | final progress = LinearPointCurve(0.28, 0.0).transform(_animation.value); 72 | 73 | final offset = Tween( 74 | begin: _defaultOffset, 75 | end: _activeOffset 76 | ).transform(offsetCurve.transform(progress)); 77 | final scaleCurveScale = 0.50; 78 | final scaleY = 0.5 + scaleCurve.transform(progress) * scaleCurveScale + (0.5 - scaleCurveScale / 2); 79 | 80 | // Create a parameterizable flat button with a fluid fill icon 81 | return GestureDetector( 82 | // We wan't to know when this button was tapped, don't bother letting out children know as well 83 | onTap: _onPressed, 84 | behavior: HitTestBehavior.opaque, 85 | child: Container( 86 | // Alignment container to the circle 87 | constraints: BoxConstraints.tight(ne), 88 | alignment: Alignment.center, 89 | child: Container( 90 | // This container just draws a circle with a certain radius and offset 91 | margin: EdgeInsets.all(ne.width / 2 - _radius), 92 | constraints: BoxConstraints.tight(Size.square(_radius * 2)), 93 | decoration: ShapeDecoration( 94 | color: AppTheme.appTheme.cardBackgroundColor(), 95 | shape: CircleBorder(), 96 | ), 97 | transform: Matrix4.translationValues(0, -offset, 0), 98 | // Create a fluid fill icon that get's filled in with a slight delay to the buttons animation 99 | child: FluidFillIcon( 100 | _iconData, 101 | LinearPointCurve(0.25, 1.0).transform(_animation.value), 102 | scaleY, 103 | ), 104 | ), 105 | ), 106 | ); 107 | } 108 | 109 | @override 110 | void didUpdateWidget(oldWidget) { 111 | setState(() { 112 | _selected = widget._selected; 113 | }); 114 | _startAnimation(); 115 | super.didUpdateWidget(oldWidget); 116 | } 117 | 118 | void _startAnimation() { 119 | if (_selected) { 120 | _animationController.forward(); 121 | } else { 122 | _animationController.reverse(); 123 | } 124 | } 125 | 126 | } 127 | 128 | -------------------------------------------------------------------------------- /lib/widget/appbar/fluid_icon.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui' as ui; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:timefly/app_theme.dart'; 5 | import 'package:timefly/utils/path_util.dart'; 6 | 7 | // Users of this class shouldn't have to explicitly import fluid_icon_data 8 | import './fluid_icon_data.dart'; 9 | export './fluid_icon_data.dart'; 10 | 11 | class FluidFillIcon extends StatelessWidget { 12 | 13 | static const double iconDataScale = 0.9; 14 | 15 | final FluidFillIconData _iconData; 16 | 17 | /// A normalzied value between 0 and 1 18 | final double _fillAmount; 19 | 20 | final double _scaleY; 21 | 22 | FluidFillIcon(FluidFillIconData iconData, double fillAmount, double scaleY) 23 | : _iconData = iconData, _fillAmount = fillAmount, _scaleY = scaleY; 24 | 25 | @override 26 | Widget build(context) { 27 | return CustomPaint( 28 | painter: _FluidFillIconPainter(_iconData.paths, _fillAmount, _scaleY), 29 | ); 30 | } 31 | } 32 | 33 | class _FluidFillIconPainter extends CustomPainter { 34 | 35 | List _paths; 36 | double _fillAmount; 37 | double _scaleY; 38 | 39 | _FluidFillIconPainter(List paths, double fillAmount, double scaleY) 40 | : _paths = paths, _fillAmount = fillAmount, _scaleY = scaleY; 41 | 42 | @override 43 | void paint(canvas, size) { 44 | final paintBackground = Paint() 45 | ..style = PaintingStyle.stroke 46 | ..strokeWidth = 2.4 47 | ..strokeCap = StrokeCap.round 48 | ..strokeJoin = StrokeJoin.round 49 | ..color = AppTheme.iconColor; 50 | 51 | final paintForeground = Paint() 52 | ..style = PaintingStyle.stroke 53 | ..strokeWidth = 2.4 54 | ..strokeCap = StrokeCap.round 55 | ..strokeJoin = StrokeJoin.round 56 | ..color = AppTheme.appTheme.selectColor(); 57 | 58 | // Scale around (0, height / 2) 59 | canvas.translate(0.0, size.height / 2); 60 | canvas.scale(1.0, _scaleY); 61 | // Center around (width / 2, height / 2) and apply the icon data scale 62 | canvas.translate(size.width / 2, 0.0); 63 | canvas.scale(FluidFillIcon.iconDataScale, FluidFillIcon.iconDataScale); 64 | 65 | // Draw the background greyed out path 66 | for (final path in _paths) { 67 | canvas.drawPath(path, paintBackground); 68 | } 69 | 70 | // Draw the black foreground path to simulate a filling effect 71 | if (_fillAmount > 0.0) { 72 | for (final path in _paths) { 73 | canvas.drawPath(extractPartialPath(path, 0.0, _fillAmount), paintForeground); 74 | } 75 | } 76 | } 77 | 78 | @override 79 | bool shouldRepaint(_FluidFillIconPainter oldWidget) { 80 | return _fillAmount != oldWidget._fillAmount; 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /lib/widget/appbar/fluid_icon_data.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui' as ui; 2 | import 'dart:math' as math; 3 | 4 | import 'package:flutter/material.dart'; 5 | 6 | class FluidFillIconData { 7 | final List paths; 8 | 9 | FluidFillIconData(this.paths); 10 | } 11 | 12 | class FluidFillIcons { 13 | static final platform = FluidFillIconData([ 14 | ui.Path() 15 | ..moveTo(0, -6) 16 | ..lineTo(10, -6), 17 | ui.Path() 18 | ..moveTo(5, 0) 19 | ..lineTo(-5, 0), 20 | ui.Path() 21 | ..moveTo(-10, 6) 22 | ..lineTo(0, 6), 23 | ]); 24 | static final window = FluidFillIconData([ 25 | ui.Path()..addRRect(RRect.fromLTRBXY(-12, -12, -2, -2, 2, 2)), 26 | ui.Path()..addRRect(RRect.fromLTRBXY(2, -12, 12, -2, 2, 2)), 27 | ui.Path()..addRRect(RRect.fromLTRBXY(-12, 2, -2, 12, 2, 2)), 28 | ui.Path()..addRRect(RRect.fromLTRBXY(2, 2, 12, 12, 2, 2)), 29 | ]); 30 | static final arrow = FluidFillIconData([ 31 | ui.Path() 32 | ..moveTo(-10, 6) 33 | ..lineTo(10, 6) 34 | ..moveTo(10, 6) 35 | ..lineTo(3, 0) 36 | ..moveTo(10, 6) 37 | ..lineTo(3, 12), 38 | ui.Path() 39 | ..moveTo(10, -6) 40 | ..lineTo(-10, -6) 41 | ..moveTo(-10, -6) 42 | ..lineTo(-3, 0) 43 | ..moveTo(-10, -6) 44 | ..lineTo(-3, -12), 45 | ]); 46 | static final user = FluidFillIconData([ 47 | ui.Path()..arcTo(Rect.fromLTRB(-5, -16, 5, -6), 0, 1.9 * math.pi, true), 48 | ui.Path()..arcTo(Rect.fromLTRB(-10, 0, 10, 20), 0, -1.0 * math.pi, true), 49 | ]); 50 | static final home = FluidFillIconData([ 51 | ui.Path()..addRRect(RRect.fromLTRBXY(-10, -2, 10, 10, 2, 2)), 52 | ui.Path() 53 | ..moveTo(-14, -2) 54 | ..lineTo(14, -2) 55 | ..lineTo(0, -16) 56 | ..close(), 57 | ]); 58 | 59 | static final progress = FluidFillIconData([ 60 | ui.Path() 61 | ..moveTo(-10, -10) 62 | ..lineTo(-10, 8) 63 | ..arcTo(Rect.fromCircle(center: Offset(-8, 8), radius: 2), -1 * math.pi, 64 | -0.5 * math.pi, true) 65 | ..moveTo(-8, 10) 66 | ..lineTo(10, 10), 67 | ui.Path() 68 | ..moveTo(-6.5, 2.5) 69 | ..lineTo(0, -5) 70 | ..lineTo(4, 0) 71 | ..lineTo(10, -9), 72 | ]); 73 | } 74 | -------------------------------------------------------------------------------- /lib/widget/calendar_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:timefly/app_theme.dart'; 3 | import 'package:timefly/models/habit.dart'; 4 | import 'package:timefly/utils/date_util.dart'; 5 | import 'package:timefly/utils/habit_util.dart'; 6 | 7 | class CalendarView extends StatefulWidget { 8 | final DateTime currentDay; 9 | 10 | final Habit habit; 11 | 12 | final double Function() caculatorHeight; 13 | 14 | final Map> records; 15 | 16 | const CalendarView( 17 | {Key key, 18 | this.currentDay, 19 | this.caculatorHeight, 20 | this.habit, 21 | this.records}) 22 | : super(key: key); 23 | 24 | @override 25 | _CalendarViewState createState() => _CalendarViewState(); 26 | } 27 | 28 | class _CalendarViewState extends State { 29 | List days; 30 | 31 | @override 32 | void initState() { 33 | days = DateUtil.getMonthDays( 34 | DateTime(widget.currentDay.year, widget.currentDay.month, 1)); 35 | super.initState(); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return Container( 41 | height: widget.caculatorHeight(), 42 | child: GridView.builder( 43 | physics: NeverScrollableScrollPhysics(), 44 | padding: EdgeInsets.only(left: .3, right: .3), 45 | itemCount: days.length, 46 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 47 | crossAxisCount: 7, childAspectRatio: 1.8, mainAxisSpacing: 5), 48 | itemBuilder: (context, index) { 49 | DateTime day = days[index]; 50 | if (day == null) { 51 | if (index < 7) { 52 | return Container( 53 | alignment: Alignment.center, 54 | child: Text( 55 | '${DateUtil.getWeekendString(index + 1)}', 56 | style: AppTheme.appTheme 57 | .headline1(fontWeight: FontWeight.w500, fontSize: 15), 58 | ), 59 | ); 60 | } 61 | return Container(); 62 | } 63 | return Container( 64 | decoration: getBox(days[index], index), 65 | alignment: Alignment.center, 66 | child: Text( 67 | '${day.day}', 68 | style: AppTheme.appTheme.numHeadline1( 69 | textColor: containsDay(days[index]) 70 | ? Colors.white 71 | : AppTheme.appTheme.normalColor(), 72 | fontWeight: FontWeight.bold, 73 | fontSize: 16), 74 | ), 75 | ); 76 | }), 77 | ); 78 | } 79 | 80 | BoxDecoration getBox(DateTime day, int index) { 81 | DateTime lastDay = days[index - 1]; 82 | DateTime nextDay = days.length - 1 > index ? days[index + 1] : null; 83 | 84 | if (containsDay(day)) { 85 | bool containLastDay = containsDay(lastDay); 86 | bool containNextDay = containsDay(nextDay); 87 | 88 | ///昨天和明天都有记录 89 | if (containLastDay && containNextDay) { 90 | return colorBox(); 91 | 92 | ///昨天有记录,明天没有记录 93 | } else if (containLastDay && !containNextDay) { 94 | return rightBox(); 95 | 96 | ///昨天没有记录,明天有记录 97 | } else if (!containLastDay && containNextDay) { 98 | return leftBox(); 99 | 100 | ///昨天和明天都没有记录 101 | } else { 102 | return allBox(); 103 | } 104 | } else { 105 | return norBox(); 106 | } 107 | } 108 | 109 | BoxDecoration norBox() { 110 | return BoxDecoration(); 111 | } 112 | 113 | BoxDecoration colorBox() { 114 | return BoxDecoration(color: Color(widget.habit.mainColor)); 115 | } 116 | 117 | BoxDecoration leftBox() { 118 | return BoxDecoration( 119 | shape: BoxShape.rectangle, 120 | borderRadius: BorderRadius.only( 121 | topLeft: Radius.circular(15), bottomLeft: Radius.circular(15)), 122 | color: Color(widget.habit.mainColor)); 123 | } 124 | 125 | BoxDecoration rightBox() { 126 | return BoxDecoration( 127 | shape: BoxShape.rectangle, 128 | borderRadius: BorderRadius.only( 129 | topRight: Radius.circular(15), bottomRight: Radius.circular(15)), 130 | color: Color(widget.habit.mainColor)); 131 | } 132 | 133 | BoxDecoration allBox() { 134 | return BoxDecoration( 135 | shape: BoxShape.circle, color: Color(widget.habit.mainColor)); 136 | } 137 | 138 | bool containsDay(DateTime date) { 139 | if (date == null) { 140 | return false; 141 | } 142 | bool contain = false; 143 | if (widget.records == null || widget.records.length == 0) { 144 | contain = false; 145 | } else if (widget.records 146 | .containsKey('${date.year}-${date.month}-${date.day}')) { 147 | contain = true; 148 | } 149 | return contain; 150 | } 151 | 152 | bool isSunday(DateTime date) { 153 | return date.weekday == DateTime.sunday; 154 | } 155 | 156 | bool isMonday(DateTime date) { 157 | return date.weekday == DateTime.monday; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /lib/widget/circle_progress_bar.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as Math; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | 5 | /// Draws a circular animated progress bar. 6 | class CircleProgressBar extends StatefulWidget { 7 | final Duration animationDuration; 8 | final Color backgroundColor; 9 | final Color foregroundColor; 10 | final double value; 11 | final double strokeWidth; 12 | 13 | const CircleProgressBar({ 14 | Key key, 15 | this.animationDuration, 16 | this.backgroundColor, 17 | this.strokeWidth, 18 | @required this.foregroundColor, 19 | @required this.value, 20 | }) : super(key: key); 21 | 22 | @override 23 | CircleProgressBarState createState() { 24 | return CircleProgressBarState(); 25 | } 26 | } 27 | 28 | class CircleProgressBarState extends State 29 | with SingleTickerProviderStateMixin { 30 | // Used in tweens where a backgroundColor isn't given. 31 | static const TRANSPARENT = Color(0x00000000); 32 | AnimationController _controller; 33 | 34 | Animation curve; 35 | Tween valueTween; 36 | Tween backgroundColorTween; 37 | Tween foregroundColorTween; 38 | 39 | @override 40 | void initState() { 41 | super.initState(); 42 | 43 | this._controller = AnimationController( 44 | duration: this.widget.animationDuration ?? const Duration(seconds: 1), 45 | vsync: this, 46 | ); 47 | 48 | this.curve = CurvedAnimation( 49 | parent: this._controller, 50 | curve: Curves.easeInOut, 51 | ); 52 | 53 | // Build the initial required tweens. 54 | this.valueTween = Tween( 55 | begin: 0, 56 | end: this.widget.value != null ? this.widget.value : 0, 57 | ); 58 | 59 | this._controller.forward(); 60 | } 61 | 62 | @override 63 | void didUpdateWidget(CircleProgressBar oldWidget) { 64 | super.didUpdateWidget(oldWidget); 65 | 66 | if (this.widget.value != oldWidget.value) { 67 | // Try to start with the previous tween's end value. This ensures that we 68 | // have a smooth transition from where the previous animation reached. 69 | double beginValue = 70 | this.valueTween?.evaluate(this.curve) ?? oldWidget?.value ?? 0; 71 | 72 | // Update the value tween. 73 | this.valueTween = Tween( 74 | begin: beginValue, 75 | end: this.widget.value ?? 1, 76 | ); 77 | 78 | // Clear cached color tweens when the color hasn't changed. 79 | if (oldWidget?.backgroundColor != this.widget.backgroundColor) { 80 | this.backgroundColorTween = ColorTween( 81 | begin: oldWidget?.backgroundColor ?? TRANSPARENT, 82 | end: this.widget.backgroundColor ?? TRANSPARENT, 83 | ); 84 | } else { 85 | this.backgroundColorTween = null; 86 | } 87 | 88 | if (oldWidget.foregroundColor != this.widget.foregroundColor) { 89 | this.foregroundColorTween = ColorTween( 90 | begin: oldWidget?.foregroundColor, 91 | end: this.widget.foregroundColor, 92 | ); 93 | } else { 94 | this.foregroundColorTween = null; 95 | } 96 | 97 | this._controller 98 | ..value = 0 99 | ..forward(); 100 | } 101 | } 102 | 103 | @override 104 | void dispose() { 105 | this._controller.dispose(); 106 | super.dispose(); 107 | } 108 | 109 | @override 110 | Widget build(BuildContext context) { 111 | return AspectRatio( 112 | aspectRatio: 1, 113 | child: AnimatedBuilder( 114 | animation: this.curve, 115 | child: Container(), 116 | builder: (context, child) { 117 | final backgroundColor = 118 | this.backgroundColorTween?.evaluate(this.curve) ?? 119 | this.widget.backgroundColor; 120 | final foregroundColor = 121 | this.foregroundColorTween?.evaluate(this.curve) ?? 122 | this.widget.foregroundColor; 123 | 124 | return CustomPaint( 125 | child: child, 126 | foregroundPainter: CircleProgressBarPainter( 127 | backgroundColor: backgroundColor, 128 | foregroundColor: foregroundColor, 129 | percentage: this.valueTween.evaluate(this.curve), 130 | strokeWidth: widget.strokeWidth 131 | ), 132 | ); 133 | }, 134 | ), 135 | ); 136 | } 137 | } 138 | 139 | // Draws the progress bar. 140 | class CircleProgressBarPainter extends CustomPainter { 141 | final double percentage; 142 | final double strokeWidth; 143 | final Color backgroundColor; 144 | final Color foregroundColor; 145 | 146 | CircleProgressBarPainter({ 147 | this.backgroundColor, 148 | @required this.foregroundColor, 149 | @required this.percentage, 150 | double strokeWidth, 151 | }) : this.strokeWidth = strokeWidth ?? 4; 152 | 153 | @override 154 | void paint(Canvas canvas, Size size) { 155 | final Offset center = size.center(Offset.zero); 156 | final Size constrainedSize = 157 | size - Offset(this.strokeWidth, this.strokeWidth); 158 | final shortestSide = 159 | Math.min(constrainedSize.width, constrainedSize.height); 160 | final foregroundPaint = Paint() 161 | ..color = this.foregroundColor 162 | ..strokeWidth = this.strokeWidth 163 | ..strokeCap = StrokeCap.round 164 | ..style = PaintingStyle.stroke; 165 | final radius = (shortestSide / 2); 166 | 167 | // Start at the top. 0 radians represents the right edge 168 | final double startAngle = -(2 * Math.pi * 0.25); 169 | final double sweepAngle = (2 * Math.pi * (this.percentage ?? 0)); 170 | 171 | // Don't draw the background if we don't have a background color 172 | if (this.backgroundColor != null) { 173 | final backgroundPaint = Paint() 174 | ..color = this.backgroundColor 175 | ..strokeWidth = this.strokeWidth 176 | ..style = PaintingStyle.stroke; 177 | canvas.drawCircle(center, radius, backgroundPaint); 178 | } 179 | 180 | canvas.drawArc( 181 | Rect.fromCircle(center: center, radius: radius), 182 | startAngle, 183 | sweepAngle, 184 | false, 185 | foregroundPaint, 186 | ); 187 | } 188 | 189 | @override 190 | bool shouldRepaint(CustomPainter oldDelegate) { 191 | final oldPainter = (oldDelegate as CircleProgressBarPainter); 192 | return oldPainter.percentage != this.percentage || 193 | oldPainter.backgroundColor != this.backgroundColor || 194 | oldPainter.foregroundColor != this.foregroundColor || 195 | oldPainter.strokeWidth != this.strokeWidth; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /lib/widget/clip/bottom_cliper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BottomClipper extends CustomClipper { 4 | @override 5 | Path getClip(Size size) { 6 | var path = Path(); 7 | path.lineTo(0, 0); 8 | path.lineTo(0, size.height - 50.0); 9 | var firstControlPoint = Offset(size.width / 2, size.height); 10 | var firstEdnPoint = Offset(size.width, size.height - 50.0); 11 | path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy, 12 | firstEdnPoint.dx, firstEdnPoint.dy); 13 | path.lineTo(size.width, size.height - 50.0); 14 | path.lineTo(size.width, 0); 15 | return path; 16 | } 17 | 18 | @override 19 | bool shouldReclip(CustomClipper oldClipper) { 20 | return false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/widget/custom_edit_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:timefly/app_theme.dart'; 3 | 4 | class CustomEditField extends StatefulWidget { 5 | /// Container bg 6 | final BoxDecoration containerDecoration; 7 | final BoxDecoration numDecoration; 8 | 9 | final int maxLines; 10 | final int maxLength; 11 | final double minHeight; 12 | 13 | final String hintText; 14 | final TextStyle hintTextStyle; 15 | final TextStyle textStyle; 16 | final TextStyle numTextStyle; 17 | final bool autoFucus; 18 | final TextInputType inputType; 19 | 20 | final String initValue; 21 | final ValueChanged onValueChanged; 22 | final VoidCallback onCompleted; 23 | 24 | const CustomEditField( 25 | {Key key, 26 | this.containerDecoration, 27 | this.maxLines, 28 | this.hintTextStyle, 29 | this.textStyle, 30 | this.initValue, 31 | this.onValueChanged, 32 | this.hintText, 33 | this.maxLength, 34 | this.numDecoration, 35 | this.numTextStyle, 36 | this.minHeight, 37 | this.autoFucus, 38 | this.inputType, 39 | this.onCompleted}) 40 | : super(key: key); 41 | 42 | @override 43 | _CustomEditFieldState createState() => _CustomEditFieldState(); 44 | } 45 | 46 | class _CustomEditFieldState extends State 47 | with TickerProviderStateMixin, AutomaticKeepAliveClientMixin { 48 | ///文本内容 49 | String _value = ''; 50 | TextEditingController editingController; 51 | AnimationController numAnimationController; 52 | Animation numAnimation; 53 | 54 | @override 55 | void initState() { 56 | _value = widget.initValue; 57 | editingController = TextEditingController(text: widget.initValue); 58 | numAnimationController = 59 | AnimationController(duration: Duration(milliseconds: 500), vsync: this); 60 | numAnimation = CurvedAnimation( 61 | parent: numAnimationController, curve: Curves.easeOutBack); 62 | if (widget.initValue.length > 0) { 63 | numAnimationController.forward(from: 0.3); 64 | } 65 | super.initState(); 66 | } 67 | 68 | @override 69 | Widget build(BuildContext context) { 70 | return Material( 71 | color: Colors.transparent, 72 | child: Stack( 73 | alignment: Alignment.topRight, 74 | children: [ 75 | Container( 76 | constraints: BoxConstraints( 77 | minHeight: widget.minHeight == null ? 0 : widget.minHeight), 78 | margin: EdgeInsets.only(top: 10, left: 32, right: 32), 79 | decoration: widget.containerDecoration, 80 | child: TextField( 81 | strutStyle: StrutStyle(height: 1.5), 82 | controller: editingController, 83 | showCursor: true, 84 | autofocus: widget.autoFucus == null ? false : widget.autoFucus, 85 | style: widget.textStyle, 86 | maxLength: widget.maxLength, 87 | decoration: InputDecoration( 88 | hintStyle: widget.hintTextStyle, 89 | hintText: widget.hintText, 90 | fillColor: Colors.transparent, 91 | filled: true, 92 | counterText: '', 93 | enabledBorder: OutlineInputBorder( 94 | borderSide: 95 | BorderSide(width: 0, color: Colors.transparent), 96 | borderRadius: BorderRadius.all(Radius.circular(15))), 97 | focusedBorder: OutlineInputBorder( 98 | borderSide: 99 | BorderSide(width: 0, color: Colors.transparent), 100 | borderRadius: BorderRadius.all(Radius.circular(15)))), 101 | maxLines: (widget.maxLines == null || widget.maxLines == 1) 102 | ? null 103 | : widget.maxLines, 104 | keyboardType: widget.inputType == null 105 | ? (widget.maxLines == null || widget.maxLines == 1) 106 | ? TextInputType.name 107 | : TextInputType.multiline 108 | : widget.inputType, 109 | cursorColor: AppTheme.appTheme.grandientColorStart(), 110 | onChanged: (value) async { 111 | setState(() { 112 | _value = value; 113 | widget.onValueChanged(value); 114 | }); 115 | if (value.length == 1) { 116 | numAnimationController.forward(from: 0.3); 117 | } else if (value.length > 1) { 118 | numAnimationController.forward(from: 0.3); 119 | } else { 120 | numAnimationController.reverse(from: 0.3); 121 | } 122 | }, 123 | onEditingComplete: widget.onCompleted ?? () { 124 | FocusScope.of(context).requestFocus(FocusNode()); 125 | }, 126 | ), 127 | ), 128 | ScaleTransition( 129 | scale: numAnimation, 130 | child: Padding( 131 | padding: EdgeInsets.only(right: 25), 132 | child: Container( 133 | alignment: Alignment.center, 134 | decoration: widget.numDecoration, 135 | width: 50, 136 | height: 35, 137 | child: Text( 138 | '${_value.length}/${widget.maxLength}', 139 | style: widget.numTextStyle, 140 | ), 141 | )), 142 | ), 143 | ], 144 | ), 145 | ); 146 | } 147 | 148 | @override 149 | void dispose() { 150 | editingController.dispose(); 151 | numAnimationController.dispose(); 152 | super.dispose(); 153 | } 154 | 155 | @override 156 | bool get wantKeepAlive => true; 157 | } 158 | -------------------------------------------------------------------------------- /lib/widget/float_modal.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; 4 | import 'package:timefly/app_theme.dart'; 5 | 6 | class FloatingModal extends StatelessWidget { 7 | final Widget child; 8 | 9 | const FloatingModal({Key key, this.child}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Column( 14 | mainAxisSize: MainAxisSize.min, 15 | crossAxisAlignment: CrossAxisAlignment.center, 16 | children: [ 17 | SizedBox(height: 18), 18 | SafeArea( 19 | bottom: false, 20 | child: Container( 21 | height: 5, 22 | width: 45, 23 | decoration: BoxDecoration( 24 | color: AppTheme.appTheme.containerBackgroundColor(), 25 | borderRadius: BorderRadius.circular(6)), 26 | ), 27 | ), 28 | SizedBox(height: 8), 29 | Flexible( 30 | flex: 1, 31 | fit: FlexFit.loose, 32 | child: ClipRRect( 33 | borderRadius: BorderRadius.only( 34 | topLeft: Radius.circular(45), topRight: Radius.circular(45)), 35 | child: Container( 36 | decoration: BoxDecoration( 37 | color: Theme.of(context).scaffoldBackgroundColor, 38 | boxShadow: [ 39 | BoxShadow( 40 | blurRadius: 10, 41 | color: Colors.black12, 42 | spreadRadius: 5) 43 | ]), 44 | child: MediaQuery.removePadding( 45 | context: context, removeTop: true, child: child)), 46 | ), 47 | ), 48 | ]); 49 | } 50 | } 51 | 52 | Future showFloatingModalBottomSheet({ 53 | @required BuildContext context, 54 | @required WidgetBuilder builder, 55 | Color barrierColor, 56 | }) async { 57 | final result = await showCustomModalBottomSheet( 58 | context: context, 59 | builder: builder, 60 | barrierColor: barrierColor, 61 | containerWidget: (_, animation, child) => FloatingModal( 62 | child: child, 63 | ), 64 | expand: true, 65 | enableDrag: true, 66 | ); 67 | 68 | return result; 69 | } 70 | -------------------------------------------------------------------------------- /lib/widget/tab_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | class BorderTabIndicator extends Decoration { 5 | BorderTabIndicator({this.indicatorHeight, this.textScaleFactor}) : super(); 6 | 7 | final double indicatorHeight; 8 | final double textScaleFactor; 9 | 10 | @override 11 | _BorderPainter createBoxPainter([VoidCallback onChanged]) { 12 | return _BorderPainter(this, indicatorHeight, textScaleFactor, onChanged); 13 | } 14 | } 15 | 16 | class _BorderPainter extends BoxPainter { 17 | _BorderPainter( 18 | this.decoration, 19 | this.indicatorHeight, 20 | this.textScaleFactor, 21 | VoidCallback onChanged, 22 | ) : assert(decoration != null), 23 | assert(indicatorHeight >= 0), 24 | super(onChanged); 25 | 26 | final BorderTabIndicator decoration; 27 | final double indicatorHeight; 28 | final double textScaleFactor; 29 | 30 | @override 31 | void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { 32 | assert(configuration != null); 33 | assert(configuration.size != null); 34 | final horizontalInset = 16 - 4 * textScaleFactor; 35 | final rect = Offset(offset.dx + horizontalInset, 36 | (configuration.size.height / 2) - indicatorHeight / 2 - 1) & 37 | Size(configuration.size.width - 2 * horizontalInset, indicatorHeight); 38 | final paint = Paint(); 39 | paint.color = Colors.white; 40 | paint.style = PaintingStyle.stroke; 41 | paint.strokeWidth = 2; 42 | canvas.drawRRect( 43 | RRect.fromRectAndRadius(rect, const Radius.circular(56)), 44 | paint, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: timefly 2 | description: How time flies. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.7.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | bloc: ^7.0.0 27 | flutter_bloc: ^7.0.1 28 | equatable: ^2.0.2 29 | rxdart: ^0.27.0 30 | sqflite: 31 | path: 32 | modal_bottom_sheet: ^2.0.0 33 | flutter_svg: ^0.22.0 34 | sleek_circular_slider: ^2.0.0 35 | flutter_local_notifications: ^6.0.0 36 | time: ^2.0.0 37 | shared_preferences: any 38 | timeline_tile: ^2.0.0 39 | flutter_slidable: ^0.6.0 40 | flash: ^2.0.0 41 | fl_chart: ^0.40.0 42 | alarm_plugin: 43 | git: 44 | url: https://github.com/designDo/alarm_plugin.git 45 | ref: 'master' 46 | 47 | # The following adds the Cupertino Icons font to your application. 48 | # Use with the CupertinoIcons class for iOS style icons. 49 | cupertino_icons: any 50 | 51 | dev_dependencies: 52 | flutter_test: 53 | sdk: flutter 54 | 55 | # For information on the generic Dart part of this file, see the 56 | # following page: https://dart.dev/tools/pub/pubspec 57 | 58 | # The following section is specific to Flutter. 59 | flutter: 60 | 61 | # The following line ensures that the Material Icons font is 62 | # included with your application, so that you can use the icons in 63 | # the material Icons class. 64 | uses-material-design: true 65 | 66 | 67 | # To add assets to your application, add an assets section, like this: 68 | assets: 69 | - assets/images/ 70 | - assets/ 71 | 72 | # An image asset can refer to one or more resolution-specific "variants", see 73 | # https://flutter.dev/assets-and-images/#resolution-aware. 74 | 75 | # For details regarding adding assets from package dependencies, see 76 | # https://flutter.dev/assets-and-images/#from-packages 77 | 78 | # To add custom fonts to your application, add a fonts section here, 79 | # in this "flutter" section. Each entry in this list should have a 80 | # "family" key with the font family name, and a "fonts" key with a 81 | # list giving the asset and other descriptors for the font. For 82 | # example: 83 | fonts: 84 | - family: MaShanZheng 85 | fonts: 86 | - asset: fonts/MaShanZheng-Regular.ttf 87 | - family: Montserrat 88 | fonts: 89 | - asset: fonts/Montserrat-Regular.ttf 90 | - asset: fonts/Montserrat-Bold.ttf 91 | weight: 700 92 | # - asset: fonts/Schyler-Italic.ttf 93 | # style: italic 94 | # - family: Trajan Pro 95 | # fonts: 96 | # - asset: fonts/TrajanPro.ttf 97 | # - asset: fonts/TrajanPro_Bold.ttf 98 | # weight: 700 99 | # 100 | # For details regarding fonts from package dependencies, 101 | # see https://flutter.dev/custom-fonts/#from-packages 102 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:timefly/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------