├── .gradle
├── 5.2.1
│ ├── gc.properties
│ ├── fileChanges
│ │ └── last-build.bin
│ └── fileHashes
│ │ └── fileHashes.lock
├── vcs-1
│ └── gc.properties
└── buildOutputCleanup
│ ├── cache.properties
│ └── buildOutputCleanup.lock
├── assets
├── app-logo.png
├── welcome-1.png
├── welcome-2.png
├── welcome-3.png
├── no-habit-dark.png
├── no-habit-light.png
└── topics
│ ├── DoogieDoodle.png
│ ├── IceCreamDoodle.png
│ ├── ReadingDoodle.png
│ ├── SwingingDoodle.png
│ ├── MeditatingDoodle.png
│ ├── StrollingDoodle.png
│ ├── dark
│ ├── DoogieDoodle.png
│ ├── IceCreamDoodle.png
│ ├── ReadingDoodle.png
│ ├── SwingingDoodle.png
│ ├── MeditatingDoodle.png
│ └── StrollingDoodle.png
│ └── light
│ ├── DoogieDoodle.png
│ ├── ReadingDoodle.png
│ ├── IceCreamDoodle.png
│ ├── MeditatingDoodle.png
│ ├── StrollingDoodle.png
│ └── SwingingDoodle.png
├── marketing
├── app-logo.png
├── gh-logo.png
├── gh-logo.xcf
├── sm-graphics.png
├── sm-graphics.xcf
├── welcome-1.png
├── welcome-2.png
├── welcome-3.png
├── ad-campaign-1.png
├── color-palete.png
├── no-habit-dark.png
├── screenshot-0.png
├── screenshot-0.xcf
├── screenshot-1.png
├── screenshot-2.png
├── screenshot-3.png
├── screenshot-4.png
├── feature-graphics.png
├── feature-graphics.xcf
├── no-habit-light.png
├── gh-feature-graphic.png
└── gh-feature-graphic.xcf
├── lib
├── domain
│ ├── Topics.dart
│ ├── TopicHabits.dart
│ ├── TimeArea.dart
│ └── Habit.dart
├── data
│ ├── domain
│ │ ├── ServiceLastRun.dart
│ │ └── HabitLastRunData.dart
│ ├── provider
│ │ ├── NotificationProvider.dart
│ │ ├── WeekDateProvider.dart
│ │ ├── WorkManagerProvider.dart
│ │ ├── ServiceLastRunProvider.dart
│ │ ├── ProviderFactory.dart
│ │ ├── HabitLastRunDataProvider.dart
│ │ ├── SettingsProvider.dart
│ │ ├── WidgetDataProvider.dart
│ │ └── HabitMasterProvider.dart
│ └── MockDataFactory.dart
├── widgets
│ ├── new
│ │ ├── SelectFromDate.dart
│ │ ├── SelectTimeOfDay.dart
│ │ ├── SelectReminder.dart
│ │ └── SelectRepeat.dart
│ ├── basic
│ │ ├── BasicTile.dart
│ │ ├── BottomNavBar.dart
│ │ ├── BaseSelectionAddableTile.dart
│ │ ├── BaseSelectionTile.dart
│ │ ├── BarChart.dart
│ │ ├── BaseSelectionRadioTile.dart
│ │ ├── BaseSelection2LineTile.dart
│ │ ├── HorizontalBarChart.dart
│ │ ├── StackedBarChart.dart
│ │ ├── LineChart.dart
│ │ ├── BaseSelectionChipsTile.dart
│ │ ├── HeatMap.dart
│ │ └── HeatMapCalendar.dart
│ ├── suggest
│ │ ├── HabitOptionTile.dart
│ │ └── TopicTile.dart
│ ├── progress
│ │ ├── StatusSummary.dart
│ │ ├── DayWisePerfomance.dart
│ │ ├── CompletionRate.dart
│ │ ├── DailyTracker.dart
│ │ └── WeeklyProgress.dart
│ ├── hprogress
│ │ ├── HabitStatusSummary.dart
│ │ ├── HabitCompletionRate.dart
│ │ ├── HabitStreaks.dart
│ │ └── HabitHeatMap.dart
│ └── today
│ │ └── HCalDayWidget.dart
├── pages
│ ├── LoadingScreen.dart
│ ├── ProgressMain.dart
│ ├── Welcome.dart
│ ├── AllHabits.dart
│ ├── SelectTopic.dart
│ └── HabitProgress.dart
└── main.dart
├── android
├── gradle.properties
├── app
│ ├── src
│ │ ├── main
│ │ │ ├── ic_launcher-playstore.png
│ │ │ ├── res
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_round.png
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_round.png
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── drawable-hdpi
│ │ │ │ │ ├── ic_stat_name.png
│ │ │ │ │ └── ic_action_name.png
│ │ │ │ ├── drawable-mdpi
│ │ │ │ │ ├── ic_stat_name.png
│ │ │ │ │ └── ic_action_name.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_round.png
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_round.png
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ ├── ic_launcher_round.png
│ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ ├── drawable-xhdpi
│ │ │ │ │ ├── ic_stat_name.png
│ │ │ │ │ └── ic_action_name.png
│ │ │ │ ├── drawable-xxhdpi
│ │ │ │ │ ├── ic_stat_name.png
│ │ │ │ │ └── ic_action_name.png
│ │ │ │ ├── drawable-xxxhdpi
│ │ │ │ │ └── ic_stat_name.png
│ │ │ │ ├── drawable-nodpi
│ │ │ │ │ ├── today_widget_preview.png
│ │ │ │ │ └── single_habit_widget_preview.png
│ │ │ │ ├── values
│ │ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ ├── theme_colors.xml
│ │ │ │ │ ├── dimens.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ ├── ic_launcher.xml
│ │ │ │ │ └── ic_launcher_round.xml
│ │ │ │ ├── values-v14
│ │ │ │ │ └── dimens.xml
│ │ │ │ ├── drawable
│ │ │ │ │ ├── accent_unselected_drawable.xml
│ │ │ │ │ ├── accent_selected_drawable.xml
│ │ │ │ │ ├── accent_selected_drawable_dark.xml
│ │ │ │ │ ├── ic_baseline_check_24.xml
│ │ │ │ │ ├── accent_unselected_drawable_dark.xml
│ │ │ │ │ ├── ic_baseline_check_24_dark.xml
│ │ │ │ │ ├── ic_baseline_check_24_white.xml
│ │ │ │ │ ├── ic_baseline_keyboard_arrow_down_24.xml
│ │ │ │ │ ├── launch_background.xml
│ │ │ │ │ └── ic_baseline_refresh_24.xml
│ │ │ │ ├── xml
│ │ │ │ │ ├── today_habits_widget_info.xml
│ │ │ │ │ └── single_habit_widget_info.xml
│ │ │ │ └── layout
│ │ │ │ │ ├── widget_loading.xml
│ │ │ │ │ ├── calender_item.xml
│ │ │ │ │ ├── calender_item_unselected.xml
│ │ │ │ │ ├── calender_item_unselected_dark.xml
│ │ │ │ │ ├── calender_item_selected.xml
│ │ │ │ │ ├── calender_item_selected_dark.xml
│ │ │ │ │ ├── single_habit_widget_configure.xml
│ │ │ │ │ ├── today_habits_widget.xml
│ │ │ │ │ ├── today_habits_widget_dark.xml
│ │ │ │ │ ├── today_widget_listitem.xml
│ │ │ │ │ ├── today_widget_listitem_dark.xml
│ │ │ │ │ ├── single_habit_widget.xml
│ │ │ │ │ └── single_habit_widget_dark.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── babanomania
│ │ │ │ │ └── CleanHabits
│ │ │ │ │ ├── TodayWidgetRemoteViewsService.kt
│ │ │ │ │ ├── SingleHabitWidgetRemoteViewsService.kt
│ │ │ │ │ ├── WidgetHelper.kt
│ │ │ │ │ ├── MainActivity.kt
│ │ │ │ │ ├── TodayWidgetRemoteViewsFactory.kt
│ │ │ │ │ ├── SingleHabitWidgetRemoteViewsFactory.kt
│ │ │ │ │ └── TodayHabitsWidgetProvider.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ └── build.gradle
├── .gitignore
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── settings.gradle
└── build.gradle
├── .metadata
├── .gitignore
├── test
└── widget_test.dart
└── pubspec.yaml
/.gradle/5.2.1/gc.properties:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gradle/vcs-1/gc.properties:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gradle/5.2.1/fileChanges/last-build.bin:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/app-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/app-logo.png
--------------------------------------------------------------------------------
/assets/welcome-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/welcome-1.png
--------------------------------------------------------------------------------
/assets/welcome-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/welcome-2.png
--------------------------------------------------------------------------------
/assets/welcome-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/welcome-3.png
--------------------------------------------------------------------------------
/.gradle/buildOutputCleanup/cache.properties:
--------------------------------------------------------------------------------
1 | #Fri Aug 14 22:34:55 IST 2020
2 | gradle.version=5.2.1
3 |
--------------------------------------------------------------------------------
/marketing/app-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/app-logo.png
--------------------------------------------------------------------------------
/marketing/gh-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/gh-logo.png
--------------------------------------------------------------------------------
/marketing/gh-logo.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/gh-logo.xcf
--------------------------------------------------------------------------------
/assets/no-habit-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/no-habit-dark.png
--------------------------------------------------------------------------------
/assets/no-habit-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/no-habit-light.png
--------------------------------------------------------------------------------
/marketing/sm-graphics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/sm-graphics.png
--------------------------------------------------------------------------------
/marketing/sm-graphics.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/sm-graphics.xcf
--------------------------------------------------------------------------------
/marketing/welcome-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/welcome-1.png
--------------------------------------------------------------------------------
/marketing/welcome-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/welcome-2.png
--------------------------------------------------------------------------------
/marketing/welcome-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/welcome-3.png
--------------------------------------------------------------------------------
/marketing/ad-campaign-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/ad-campaign-1.png
--------------------------------------------------------------------------------
/marketing/color-palete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/color-palete.png
--------------------------------------------------------------------------------
/marketing/no-habit-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/no-habit-dark.png
--------------------------------------------------------------------------------
/marketing/screenshot-0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/screenshot-0.png
--------------------------------------------------------------------------------
/marketing/screenshot-0.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/screenshot-0.xcf
--------------------------------------------------------------------------------
/marketing/screenshot-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/screenshot-1.png
--------------------------------------------------------------------------------
/marketing/screenshot-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/screenshot-2.png
--------------------------------------------------------------------------------
/marketing/screenshot-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/screenshot-3.png
--------------------------------------------------------------------------------
/marketing/screenshot-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/screenshot-4.png
--------------------------------------------------------------------------------
/assets/topics/DoogieDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/DoogieDoodle.png
--------------------------------------------------------------------------------
/marketing/feature-graphics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/feature-graphics.png
--------------------------------------------------------------------------------
/marketing/feature-graphics.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/feature-graphics.xcf
--------------------------------------------------------------------------------
/marketing/no-habit-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/no-habit-light.png
--------------------------------------------------------------------------------
/assets/topics/IceCreamDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/IceCreamDoodle.png
--------------------------------------------------------------------------------
/assets/topics/ReadingDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/ReadingDoodle.png
--------------------------------------------------------------------------------
/assets/topics/SwingingDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/SwingingDoodle.png
--------------------------------------------------------------------------------
/lib/domain/Topics.dart:
--------------------------------------------------------------------------------
1 | class Topics {
2 | String title, assetPath;
3 | Topics({this.title, this.assetPath});
4 | }
5 |
--------------------------------------------------------------------------------
/marketing/gh-feature-graphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/gh-feature-graphic.png
--------------------------------------------------------------------------------
/marketing/gh-feature-graphic.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/marketing/gh-feature-graphic.xcf
--------------------------------------------------------------------------------
/assets/topics/MeditatingDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/MeditatingDoodle.png
--------------------------------------------------------------------------------
/assets/topics/StrollingDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/StrollingDoodle.png
--------------------------------------------------------------------------------
/assets/topics/dark/DoogieDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/dark/DoogieDoodle.png
--------------------------------------------------------------------------------
/assets/topics/dark/IceCreamDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/dark/IceCreamDoodle.png
--------------------------------------------------------------------------------
/assets/topics/dark/ReadingDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/dark/ReadingDoodle.png
--------------------------------------------------------------------------------
/assets/topics/dark/SwingingDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/dark/SwingingDoodle.png
--------------------------------------------------------------------------------
/assets/topics/light/DoogieDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/light/DoogieDoodle.png
--------------------------------------------------------------------------------
/assets/topics/light/ReadingDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/light/ReadingDoodle.png
--------------------------------------------------------------------------------
/.gradle/5.2.1/fileHashes/fileHashes.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/.gradle/5.2.1/fileHashes/fileHashes.lock
--------------------------------------------------------------------------------
/assets/topics/dark/MeditatingDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/dark/MeditatingDoodle.png
--------------------------------------------------------------------------------
/assets/topics/dark/StrollingDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/dark/StrollingDoodle.png
--------------------------------------------------------------------------------
/assets/topics/light/IceCreamDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/light/IceCreamDoodle.png
--------------------------------------------------------------------------------
/assets/topics/light/MeditatingDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/light/MeditatingDoodle.png
--------------------------------------------------------------------------------
/assets/topics/light/StrollingDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/light/StrollingDoodle.png
--------------------------------------------------------------------------------
/assets/topics/light/SwingingDoodle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/assets/topics/light/SwingingDoodle.png
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/android/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/.gradle/buildOutputCleanup/buildOutputCleanup.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/.gradle/buildOutputCleanup/buildOutputCleanup.lock
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_stat_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/drawable-hdpi/ic_stat_name.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_stat_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/drawable-mdpi/ic_stat_name.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_action_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/drawable-hdpi/ic_action_name.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_action_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/drawable-mdpi/ic_action_name.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_stat_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/drawable-xhdpi/ic_stat_name.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_stat_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_stat_name.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_action_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/drawable-xhdpi/ic_action_name.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_action_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_action_name.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_stat_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_stat_name.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-nodpi/today_widget_preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/drawable-nodpi/today_widget_preview.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #E8DED2
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-nodpi/single_habit_widget_preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clean-apps/CleanHabits/HEAD/android/app/src/main/res/drawable-nodpi/single_habit_widget_preview.png
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Aug 29 16:26:42 IST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Today
4 | Add Widget
5 | All
6 | Configure Widget
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-v14/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 | 0dp
9 |
10 |
--------------------------------------------------------------------------------
/.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: 8af6b2f038c1172e61d418869363a28dffec3cb4
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/accent_unselected_drawable.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/accent_selected_drawable.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/accent_selected_drawable_dark.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_baseline_check_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/accent_unselected_drawable_dark.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_baseline_check_24_dark.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_baseline_check_24_white.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/babanomania/CleanHabits/TodayWidgetRemoteViewsService.kt:
--------------------------------------------------------------------------------
1 | package com.babanomania.CleanHabits
2 |
3 | import android.content.Intent
4 | import android.widget.RemoteViewsService
5 |
6 | class TodayWidgetRemoteViewsService : RemoteViewsService() {
7 | override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
8 | return TodayWidgetRemoteViewsFactory(this.applicationContext, intent)
9 | }
10 | }
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/babanomania/CleanHabits/SingleHabitWidgetRemoteViewsService.kt:
--------------------------------------------------------------------------------
1 | package com.babanomania.CleanHabits
2 |
3 | import android.content.Intent
4 | import android.widget.RemoteViewsService
5 |
6 | class SingleHabitWidgetRemoteViewsService : RemoteViewsService() {
7 | override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
8 | return SingleHabitWidgetRemoteViewsFactory(this.applicationContext, intent)
9 | }
10 | }
--------------------------------------------------------------------------------
/android/app/src/main/res/values/theme_colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF056676
4 | #FF4db6ac
5 | #604db6ac
6 | #FFde0000
7 | #FFffaaaa
8 | #60ffd5d5
9 | #FF272727
10 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/ic_baseline_refresh_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/xml/today_habits_widget_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/lib/domain/TopicHabits.dart:
--------------------------------------------------------------------------------
1 | import 'package:CleanHabits/widgets/new/SelectRepeat.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class TopicHabits {
5 | final IconData icon;
6 | final String title;
7 | final bool isYNType;
8 | final int timesTarget;
9 | Repeats repeat = Repeats();
10 | final String timesTargetType;
11 | final String timeOfDay;
12 |
13 | TopicHabits({
14 | this.icon,
15 | this.title,
16 | this.isYNType = true,
17 | this.timesTarget = 1,
18 | this.timesTargetType = '',
19 | this.repeat,
20 | this.timeOfDay = "All Day",
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/widget_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 | 20dp
9 | 48dp
10 | 48dp
11 | 48dp
12 | 16dp
13 | 10dp
14 |
15 |
16 |
--------------------------------------------------------------------------------
/android/app/src/main/res/xml/single_habit_widget_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | include ':app'
6 |
7 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
8 | def properties = new Properties()
9 |
10 | assert localPropertiesFile.exists()
11 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
12 |
13 | def flutterSdkPath = properties.getProperty("flutter.sdk")
14 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
15 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
16 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/calender_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.4.0'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.google.gms:google-services:4.3.3'
10 | classpath 'com.android.tools.build:gradle:4.0.1'
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | google()
18 | jcenter()
19 | }
20 | }
21 |
22 | rootProject.buildDir = '../build'
23 | subprojects {
24 | project.buildDir = "${rootProject.buildDir}/${project.name}"
25 | }
26 | subprojects {
27 | project.evaluationDependsOn(':app')
28 | }
29 |
30 | task clean(type: Delete) {
31 | delete rootProject.buildDir
32 | }
33 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/calender_item_unselected.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/calender_item_unselected_dark.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/calender_item_selected.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/calender_item_selected_dark.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.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 | .dart_tool/
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | /build/
32 |
33 | # Web related
34 | lib/generated_plugin_registrant.dart
35 |
36 | # Symbolication related
37 | app.*.symbols
38 |
39 | # Obfuscation related
40 | app.*.map.json
41 |
42 | # Exceptions to above rules.
43 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
44 |
45 | android/key.properties
46 | android/app/release/*
47 | android/app/google-services.json
--------------------------------------------------------------------------------
/lib/data/domain/ServiceLastRun.dart:
--------------------------------------------------------------------------------
1 | import 'package:intl/intl.dart';
2 |
3 | final String tableServiceLastRun = 'service_last_run';
4 |
5 | final String columnId = '_id';
6 | final String columnLastUpdated = 'last_updated'; // unix epoch
7 |
8 | var fmt = DateFormat("yyyy-MM-dd");
9 |
10 | class ServiceLastRun {
11 | int id;
12 | DateTime lastUpdated;
13 |
14 | Map toMap() {
15 | var map = {
16 | columnLastUpdated:
17 | lastUpdated == null ? null : lastUpdated.millisecondsSinceEpoch,
18 | };
19 |
20 | if (id != null) {
21 | map[columnId] = id;
22 | }
23 |
24 | return map;
25 | }
26 |
27 | ServiceLastRun();
28 |
29 | ServiceLastRun.fromMap(Map map) {
30 | id = map[columnId];
31 | lastUpdated = map[columnLastUpdated] == null
32 | ? null
33 | : DateTime.fromMillisecondsSinceEpoch(
34 | map[columnLastUpdated],
35 | isUtc: false,
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/single_habit_widget_configure.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
17 |
18 |
25 |
26 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/lib/widgets/new/SelectFromDate.dart:
--------------------------------------------------------------------------------
1 | import 'package:CleanHabits/widgets/basic/BaseSelection2LineTile.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:intl/intl.dart';
4 |
5 | class SelectFromDate extends StatelessWidget {
6 | final DateTime value;
7 | final ValueChanged onChange;
8 | SelectFromDate({this.value, this.onChange});
9 |
10 | void _showDialog(context) {
11 | showDatePicker(
12 | context: context,
13 | initialDate: DateTime.now(),
14 | firstDate: DateTime.now(),
15 | lastDate: DateTime.now().add(
16 | Duration(days: 365),
17 | ),
18 | ).then(this.onChange);
19 | }
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return BaseSelection2LineTile(
24 | value: value == null ? null : DateFormat('dd-MMM, EEE').format(value),
25 | icon: Icon(Icons.calendar_today),
26 | title: 'Start From',
27 | emptyText: 'Today',
28 | onTap: () => _showDialog(context),
29 | onClear: () => this.onChange(null),
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/data/domain/HabitLastRunData.dart:
--------------------------------------------------------------------------------
1 | final String tableHabitLastRunData = 'habit_last_run_data';
2 |
3 | final String columnId = '_id';
4 | final String columnHabitId = '_habit_id';
5 | final String columnLastUpdated = 'last_updated'; // unix epoch
6 |
7 | class HabitLastRunData {
8 | int id;
9 | int habitId;
10 | DateTime lastUpdated;
11 |
12 | Map toMap() {
13 | var map = {
14 | columnHabitId: habitId,
15 | columnLastUpdated:
16 | lastUpdated == null ? null : lastUpdated.millisecondsSinceEpoch,
17 | };
18 |
19 | if (id != null) {
20 | map[columnId] = id;
21 | }
22 |
23 | return map;
24 | }
25 |
26 | HabitLastRunData();
27 |
28 | HabitLastRunData.fromMap(Map map) {
29 | id = map[columnId];
30 | habitId = map[columnHabitId];
31 | lastUpdated = map[columnLastUpdated] == null
32 | ? null
33 | : DateTime.fromMillisecondsSinceEpoch(
34 | map[columnLastUpdated],
35 | isUtc: false,
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/babanomania/CleanHabits/WidgetHelper.kt:
--------------------------------------------------------------------------------
1 | package com.babanomania.CleanHabits
2 |
3 | import android.content.Context
4 |
5 | class WidgetHelper {
6 | companion object {
7 | private const val WIDGET_PREFERENCES_KEY = "widget_preferences"
8 | private const val WIDGET_HANDLE_KEY = "handle"
9 |
10 | const val CHANNEL = "com.babanomania.cleanhabits/updateWidget"
11 | const val NO_HANDLE = -1L
12 |
13 | fun setHandle(context: Context, handle: Long) {
14 | context.getSharedPreferences(
15 | WIDGET_PREFERENCES_KEY,
16 | Context.MODE_PRIVATE
17 | ).edit().apply {
18 | putLong(WIDGET_HANDLE_KEY, handle)
19 | apply()
20 | }
21 | }
22 |
23 | fun getRawHandle(context: Context): Long {
24 | return context.getSharedPreferences(
25 | WIDGET_PREFERENCES_KEY,
26 | Context.MODE_PRIVATE
27 | ).getLong(WIDGET_HANDLE_KEY, NO_HANDLE)
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/lib/widgets/basic/BasicTile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class BasicTile extends StatelessWidget {
4 | final String title;
5 | final String subtitle1;
6 | final String subtitle2;
7 | BasicTile({this.title, this.subtitle1, this.subtitle2});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | var _theme = Theme.of(context);
12 | var accentColor = _theme.accentColor;
13 | var titleStyle = _theme.textTheme.headline3.copyWith(color: accentColor);
14 | var subtitle1Style = _theme.textTheme.subtitle1;
15 | var subtitle2Style = _theme.textTheme.subtitle2;
16 |
17 | return Expanded(
18 | child: Card(
19 | child: Padding(
20 | padding: EdgeInsets.all(15),
21 | child: Column(
22 | crossAxisAlignment: CrossAxisAlignment.start,
23 | children: [
24 | Text(title, style: titleStyle),
25 | Text(subtitle1, style: subtitle1Style),
26 | Text(subtitle2, style: subtitle2Style),
27 | ],
28 | ),
29 | ),
30 | ),
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/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:CleanHabits/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 |
--------------------------------------------------------------------------------
/lib/pages/LoadingScreen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class Loading extends StatelessWidget {
4 | @override
5 | Widget build(BuildContext context) {
6 | var _textTheme = Theme.of(context).textTheme;
7 |
8 | return new MaterialApp(
9 | home: Center(
10 | child: Scaffold(
11 | body: Center(
12 | child: Column(
13 | mainAxisAlignment: MainAxisAlignment.center,
14 | crossAxisAlignment: CrossAxisAlignment.center,
15 | children: [
16 | Image.asset(
17 | 'assets/app-logo.png',
18 | width: 250,
19 | height: 250,
20 | ),
21 | Text(
22 | 'Clean',
23 | style: _textTheme.headline2.copyWith(
24 | fontWeight: FontWeight.w300,
25 | ),
26 | ),
27 | Text(
28 | 'Habits',
29 | style: _textTheme.headline3.copyWith(
30 | color: Colors.blueGrey,
31 | fontWeight: FontWeight.bold,
32 | ),
33 | ),
34 | ],
35 | ),
36 | ),
37 | ),
38 | ),
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/data/provider/NotificationProvider.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_local_notifications/flutter_local_notifications.dart';
3 |
4 | class NotificationProvider {
5 | //
6 | Future selectNotification(String payload) async {
7 | if (payload != null) {
8 | debugPrint('notification payload: ' + payload);
9 | }
10 | }
11 |
12 | showNotificiation({String title, String body, String payload}) async {
13 | var flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
14 | await flutterLocalNotificationsPlugin.initialize(
15 | InitializationSettings(
16 | AndroidInitializationSettings('ic_stat_name'),
17 | null,
18 | ),
19 | onSelectNotification: selectNotification,
20 | );
21 | var androidPlatformChannelSpecifics = AndroidNotificationDetails(
22 | 'clean-habits-reminders',
23 | 'Reminders',
24 | 'Clean Habits Reminders',
25 | importance: Importance.Max,
26 | priority: Priority.High,
27 | ticker: 'reminders',
28 | );
29 | var platformChannelSpecifics = NotificationDetails(
30 | androidPlatformChannelSpecifics,
31 | null,
32 | );
33 | await flutterLocalNotificationsPlugin.show(
34 | 0,
35 | title,
36 | body,
37 | platformChannelSpecifics,
38 | payload: payload,
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/widgets/new/SelectTimeOfDay.dart:
--------------------------------------------------------------------------------
1 | import 'package:CleanHabits/data/provider/ProviderFactory.dart';
2 | import 'package:CleanHabits/widgets/basic/BaseSelection2LineTile.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class SelectTimeOfDay extends StatelessWidget {
6 | final String value;
7 | final ValueChanged onChange;
8 |
9 | final sp = ProviderFactory.settingsProvider;
10 |
11 | SelectTimeOfDay({this.value, this.onChange});
12 |
13 | void _showDialog(context) {
14 | final List options = sp.timeArea.map((ta) => ta.area).toList();
15 | options.add('All Day');
16 |
17 | showDialog(
18 | context: context,
19 | builder: (ctxt) => SimpleDialog(
20 | title: Text('Select Time Of Day'),
21 | children: options
22 | .map((e) => ListTile(
23 | dense: true,
24 | title: Text(e),
25 | onTap: () => {
26 | this.onChange(e),
27 | Navigator.of(context).pop(),
28 | },
29 | ))
30 | .toList(),
31 | ),
32 | );
33 | }
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | return BaseSelection2LineTile(
38 | value: this.value,
39 | icon: Icon(Icons.brightness_low),
40 | title: 'Time Of Day',
41 | emptyText: 'All Day',
42 | onTap: () => _showDialog(context),
43 | onClear: () => this.onChange(null),
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/widgets/suggest/HabitOptionTile.dart:
--------------------------------------------------------------------------------
1 | import 'package:CleanHabits/domain/TopicHabits.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:font_awesome_flutter/font_awesome_flutter.dart';
4 |
5 | class HabitOptionTile extends StatelessWidget {
6 | final TopicHabits option;
7 | HabitOptionTile({this.option});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | var _displayString =
12 | option.repeat == null ? 'Everyday' : option.repeat.displayString2();
13 |
14 | return option.isYNType
15 | ? ListTile(
16 | leading: FaIcon(option.icon, size: 36),
17 | title: Text(
18 | option.title,
19 | style: TextStyle(fontWeight: FontWeight.bold),
20 | ),
21 | subtitle: _displayString == null || _displayString == ''
22 | ? Text('')
23 | : Text(_displayString),
24 | onTap: () => Navigator.pushNamed(
25 | context,
26 | "/new",
27 | arguments: option,
28 | ),
29 | )
30 | : ListTile(
31 | leading: FaIcon(option.icon, size: 36),
32 | title: Text(
33 | option.title,
34 | style: TextStyle(fontWeight: FontWeight.bold),
35 | ),
36 | subtitle: Text(
37 | "${option.timesTarget} ${option.timesTargetType} $_displayString"),
38 | onTap: () => Navigator.pushNamed(
39 | context,
40 | "/new",
41 | arguments: option,
42 | ),
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/widgets/progress/StatusSummary.dart:
--------------------------------------------------------------------------------
1 | import 'package:CleanHabits/data/ProgressStatsService.dart';
2 | import 'package:CleanHabits/widgets/basic/BasicTile.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class StatusSummary extends StatefulWidget {
6 | final ProgressStatsService statsService = ProgressStatsService();
7 | @override
8 | _StatusSummaryState createState() => _StatusSummaryState();
9 | }
10 |
11 | class _StatusSummaryState extends State {
12 | var data = StatusSummaryData(todayProgress: 0, todayTarget: 0);
13 |
14 | @override
15 | void initState() {
16 | super.initState();
17 | _loadData();
18 | }
19 |
20 | void _loadData() {
21 | widget.statsService.getStatusSummaryData().then(
22 | (value) => setState(() {
23 | data = value;
24 | }),
25 | );
26 | }
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | var descTodaysGoal = data.todayProgress > 0
31 | ? data.todayProgress == data.todayTarget
32 | ? 'Great, target completed'
33 | : 'Good, target is in-progress'
34 | : 'Start for your goals now';
35 |
36 | return Row(
37 | children: [
38 | BasicTile(
39 | title:
40 | '${data.todayProgress.toString()}/${data.todayTarget.toString()}',
41 | subtitle1: 'Today\'s Habits',
42 | subtitle2: descTodaysGoal,
43 | )
44 | ],
45 | );
46 | }
47 | }
48 |
49 | class StatusSummaryData {
50 | int todayProgress = 0;
51 | int todayTarget = 0;
52 | StatusSummaryData({this.todayProgress, this.todayTarget});
53 | }
54 |
--------------------------------------------------------------------------------
/lib/widgets/basic/BottomNavBar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class BottomNavBar extends StatelessWidget {
4 | final int index;
5 | BottomNavBar({this.index});
6 |
7 | List _navElements() {
8 | return const [
9 | BottomNavigationBarItem(
10 | icon: Icon(Icons.view_day),
11 | title: Text('Today'),
12 | ),
13 | BottomNavigationBarItem(
14 | icon: Icon(Icons.insert_chart),
15 | title: Text('Progress'),
16 | ),
17 | BottomNavigationBarItem(
18 | icon: Icon(Icons.settings),
19 | title: Text('Settings'),
20 | ),
21 | ];
22 | }
23 |
24 | _handleNavChange(context, index) {
25 | switch (index) {
26 | case 0:
27 | Navigator.of(context).popUntil((route) => route.isFirst);
28 | Navigator.popAndPushNamed(context, '/');
29 | break;
30 | case 1:
31 | Navigator.of(context).popUntil((route) => route.isFirst);
32 | Navigator.pushNamed(context, '/progress');
33 | break;
34 | case 2:
35 | Navigator.of(context).popUntil((route) => route.isFirst);
36 | Navigator.pushNamed(context, '/settings');
37 | break;
38 | }
39 | }
40 |
41 | @override
42 | Widget build(BuildContext context) {
43 | return BottomNavigationBar(
44 | backgroundColor: Theme.of(context).scaffoldBackgroundColor,
45 | elevation: 0.0,
46 | items: _navElements(),
47 | currentIndex: this.index,
48 | selectedItemColor: Theme.of(context).accentColor,
49 | onTap: (index) => _handleNavChange(context, index));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: CleanHabits
2 | description: The Minimal, Clean, Habit Tracker
3 |
4 | publish_to: "none"
5 | version: 1.3.20200920+5
6 |
7 | environment:
8 | sdk: ">=2.7.0 <3.0.0"
9 |
10 | dependencies:
11 | intl: 0.16.1
12 | sqflite: ^1.3.1
13 | workmanager: ^0.2.3
14 | settings_ui: ^0.4.0
15 | timeline_tile: 0.1.2
16 | path_provider: ^1.6.11
17 | charts_flutter: ^0.9.0
18 | flutter_slidable: ^0.5.7
19 | heatmap_calendar: ^1.2.8
20 | firebase_analytics: ^6.0.0
21 | sk_onboarding_screen: 1.0.1
22 | font_awesome_flutter: ^8.8.1
23 | flutter_calendar_carousel: ^1.4.12
24 | flutter_staggered_animations: ^0.1.2
25 | flutter_local_notifications: ^1.4.4+4
26 | shared_preferences: ">=0.5.7+3 <2.0.0"
27 |
28 | flutter:
29 | sdk: flutter
30 |
31 | dev_dependencies:
32 | flutter_test:
33 | sdk: flutter
34 |
35 | flutter:
36 | uses-material-design: true
37 | assets:
38 | - assets/app-logo.png
39 | - assets/no-habit-light.png
40 | - assets/no-habit-dark.png
41 | - assets/welcome-1.png
42 | - assets/welcome-2.png
43 | - assets/welcome-3.png
44 | - assets/topics/light/DoogieDoodle.png
45 | - assets/topics/dark/DoogieDoodle.png
46 | - assets/topics/light/MeditatingDoodle.png
47 | - assets/topics/dark/MeditatingDoodle.png
48 | - assets/topics/light/StrollingDoodle.png
49 | - assets/topics/dark/StrollingDoodle.png
50 | - assets/topics/light/IceCreamDoodle.png
51 | - assets/topics/dark/IceCreamDoodle.png
52 | - assets/topics/light/ReadingDoodle.png
53 | - assets/topics/dark/ReadingDoodle.png
54 | - assets/topics/light/SwingingDoodle.png
55 | - assets/topics/dark/SwingingDoodle.png
56 |
--------------------------------------------------------------------------------
/lib/domain/TimeArea.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:intl/intl.dart';
3 |
4 | class TimeArea {
5 | String area;
6 | TimeOfDay startTime;
7 | final _format = DateFormat("HH:mm");
8 | TimeArea({this.area, this.startTime});
9 |
10 | IconData get icon {
11 | switch (area) {
12 | case "Morning":
13 | return Icons.landscape;
14 | break;
15 | case "Afternoon":
16 | return Icons.brightness_5;
17 | break;
18 | case "Evening":
19 | return Icons.brightness_4;
20 | break;
21 | case "Night":
22 | return Icons.brightness_2;
23 | break;
24 | case "All Day":
25 | return Icons.view_day;
26 | break;
27 | default:
28 | return Icons.lightbulb_outline;
29 | }
30 | }
31 |
32 | Map toJson() {
33 | Map jsonData = Map();
34 | jsonData['area'] = area;
35 |
36 | final _now = new DateTime.now();
37 | jsonData['startTime'] = _format.format(
38 | DateTime(
39 | _now.year,
40 | _now.month,
41 | _now.day,
42 | startTime.hour,
43 | startTime.minute,
44 | ),
45 | );
46 |
47 | return jsonData;
48 | }
49 |
50 | static TimeArea fromJson(Map data) {
51 | String sArea = data['area'];
52 | TimeOfDay sStartTime = TimeOfDay(
53 | hour: int.parse(data['startTime'].split(":")[0]),
54 | minute: int.parse(data['startTime'].split(":")[1]),
55 | );
56 |
57 | return TimeArea(
58 | area: sArea,
59 | startTime: sStartTime,
60 | );
61 | }
62 |
63 | String toString() {
64 | return toJson().toString();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/today_habits_widget.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
12 |
22 |
23 |
24 |
26 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/lib/widgets/basic/BaseSelectionAddableTile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class BaseSelectionAddableTile extends StatelessWidget {
4 | //
5 | final String value;
6 | final VoidCallback onTap;
7 | final VoidCallback onClear;
8 | final Icon icon;
9 | final String title;
10 | final String emptyText;
11 |
12 | BaseSelectionAddableTile({
13 | this.value,
14 | this.onTap,
15 | this.onClear,
16 | this.icon,
17 | this.title,
18 | this.emptyText,
19 | });
20 | Widget _tile(Icon _icon, String prefix, String title, BuildContext context) {
21 | var _theme = Theme.of(context);
22 | var _darkMode = _theme.brightness == Brightness.dark;
23 |
24 | return Container(
25 | decoration: BoxDecoration(
26 | color: _darkMode
27 | ? Colors.grey.withOpacity(0.25)
28 | : Theme.of(context).scaffoldBackgroundColor,
29 | border: Border(
30 | top: BorderSide(color: Colors.grey.withOpacity(0.5), width: 0.5),
31 | bottom: BorderSide(color: Colors.grey.withOpacity(0.5), width: 0.5),
32 | ),
33 | ),
34 | child: ListTile(
35 | leading: _icon,
36 | title: Text('$prefix $title'),
37 | trailing: IconButton(
38 | color: Theme.of(context).accentColor,
39 | icon: Icon(Icons.add_circle_outline),
40 | onPressed: () => this.onTap(),
41 | ),
42 | onTap: () => this.onTap(),
43 | ),
44 | );
45 | }
46 |
47 | @override
48 | Widget build(BuildContext context) {
49 | return _tile(
50 | this.icon,
51 | this.value == null ? this.emptyText : this.title,
52 | this.value == null ? '' : this.value,
53 | context,
54 | );
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/data/provider/WeekDateProvider.dart:
--------------------------------------------------------------------------------
1 | class WeekDateProvider {
2 | static int currentWeek({bool startWithMonday = true}) {
3 | return weekOfYear(
4 | date: DateTime.now(),
5 | startWithMonday: startWithMonday,
6 | );
7 | }
8 |
9 | static int weekOfYear({bool startWithMonday = true, DateTime date}) {
10 | //
11 | DateTime weekStartDay = weekStart(
12 | date: date,
13 | startWithMonday: startWithMonday,
14 | );
15 |
16 | DateTime first = weekYearStartDate(weekStartDay.year);
17 |
18 | int week = 1 + (weekStartDay.difference(first).inDays / 7).round();
19 |
20 | if (week == 53 && DateTime(weekStartDay.year, 12, 31).weekday < 4) week = 1;
21 |
22 | return week;
23 | }
24 |
25 | static DateTime weekStart({bool startWithMonday = true, DateTime date}) {
26 | // This is ugly, but to avoid problems with daylight saving
27 | DateTime monday = DateTime.utc(date.year, date.month, date.day);
28 | monday = monday.subtract(Duration(days: monday.weekday - 1));
29 |
30 | if (startWithMonday) {
31 | return date.weekday == DateTime.monday
32 | ? DateTime.utc(date.year, date.month, date.day)
33 | : monday;
34 | //
35 | } else {
36 | DateTime sunday = monday.subtract(Duration(days: 1));
37 | return date.weekday == DateTime.sunday
38 | ? DateTime.utc(date.year, date.month, date.day)
39 | : sunday;
40 | }
41 | }
42 |
43 | static DateTime weekYearStartDate(int year) {
44 | final firstDayOfYear = DateTime.utc(year, 1, 1);
45 | final dayOfWeek = firstDayOfYear.weekday;
46 |
47 | return firstDayOfYear.add(
48 | Duration(
49 | days: (dayOfWeek <= DateTime.thursday ? 1 : 8) - dayOfWeek,
50 | ),
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/today_habits_widget_dark.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
13 |
23 |
24 |
25 |
27 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/lib/widgets/basic/BaseSelectionTile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class BaseSelectionTile extends StatelessWidget {
4 | //
5 | final String value;
6 | final VoidCallback onTap;
7 | final VoidCallback onClear;
8 | final Icon icon;
9 | final String title;
10 | final String emptyText;
11 |
12 | BaseSelectionTile({
13 | this.value,
14 | this.onTap,
15 | this.onClear,
16 | this.icon,
17 | this.title,
18 | this.emptyText,
19 | });
20 | Widget _tile(Icon _icon, String prefix, String title, BuildContext context) {
21 | var _theme = Theme.of(context);
22 | var _darkMode = _theme.brightness == Brightness.dark;
23 |
24 | return Container(
25 | decoration: BoxDecoration(
26 | color: _darkMode
27 | ? Colors.grey.withOpacity(0.25)
28 | : Theme.of(context).scaffoldBackgroundColor,
29 | border: Border(
30 | top: BorderSide(color: Colors.grey.withOpacity(0.5), width: 0.5),
31 | bottom: BorderSide(color: Colors.grey.withOpacity(0.5), width: 0.5),
32 | ),
33 | ),
34 | child: ListTile(
35 | leading: _icon,
36 | title: Text('$prefix $title'),
37 | trailing: title == null || title == ''
38 | ? Text('')
39 | : IconButton(
40 | color: Theme.of(context).accentColor,
41 | icon: Icon(Icons.cancel),
42 | onPressed: () => this.onClear(),
43 | ),
44 | onTap: () => this.onTap(),
45 | ),
46 | );
47 | }
48 |
49 | @override
50 | Widget build(BuildContext context) {
51 | return _tile(
52 | this.icon,
53 | this.value == null ? this.emptyText : this.title,
54 | this.value == null ? '' : this.value,
55 | context,
56 | );
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/lib/domain/Habit.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class Habit {
4 | int id;
5 | String title;
6 | List reminder;
7 |
8 | bool isYNType;
9 | bool ynCompleted;
10 |
11 | int timesTarget;
12 | int timesProgress;
13 | String timesTargetType;
14 |
15 | String timeOfDay;
16 |
17 | bool isSkipped;
18 |
19 | static Habit newYNHabit({
20 | int id,
21 | String title,
22 | List reminder,
23 | bool completed,
24 | String timeOfDay,
25 | }) {
26 | Habit newHabit = new Habit();
27 | newHabit.id = id;
28 | newHabit.title = title;
29 | newHabit.reminder = reminder;
30 | newHabit.isYNType = true;
31 | newHabit.ynCompleted = completed;
32 | newHabit.timesTarget = 1;
33 | newHabit.timesProgress = completed ? 1 : 0;
34 | newHabit.timesTargetType = null;
35 | newHabit.timeOfDay = timeOfDay;
36 | newHabit.isSkipped = false;
37 |
38 | return newHabit;
39 | }
40 |
41 | static Habit newTimesHabit({
42 | int id,
43 | String title,
44 | List reminder,
45 | int target,
46 | int completed,
47 | String targetType,
48 | String timeOfDay,
49 | }) {
50 | Habit newHabit = new Habit();
51 | newHabit.id = id;
52 | newHabit.title = title;
53 | newHabit.reminder = reminder;
54 | newHabit.isYNType = false;
55 | newHabit.ynCompleted = completed == target;
56 | newHabit.timesTarget = target;
57 | newHabit.timesProgress = completed;
58 | newHabit.timesTargetType = targetType;
59 | newHabit.timeOfDay = timeOfDay;
60 | newHabit.isSkipped = false;
61 |
62 | return newHabit;
63 | }
64 |
65 | @override
66 | String toString() {
67 | return '[$id] $title -> isSimple: $isYNType -> progress: $timesProgress -> ynCompleted: $ynCompleted';
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/lib/widgets/basic/BarChart.dart:
--------------------------------------------------------------------------------
1 | import 'package:charts_flutter/flutter.dart' as charts;
2 | import 'package:flutter/material.dart';
3 |
4 | class BarChart extends StatelessWidget {
5 | final List seriesList;
6 | final bool animate;
7 |
8 | BarChart(this.seriesList, {this.animate});
9 |
10 | factory BarChart.withData(
11 | String id,
12 | List data,
13 | BuildContext context,
14 | ) {
15 | return new BarChart(_createData(id, data, context), animate: true);
16 | }
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return new charts.BarChart(
21 | seriesList,
22 | animate: animate,
23 | defaultRenderer: new charts.BarRendererConfig(
24 | cornerStrategy: const charts.ConstCornerStrategy(12),
25 | ),
26 | );
27 | }
28 |
29 | static List> _createData(
30 | id,
31 | List data,
32 | BuildContext context,
33 | ) {
34 | var _theme = Theme.of(context);
35 | var accentColor = _theme.accentColor;
36 | var accentHex =
37 | '#${(accentColor.value & 0xFFFFFF).toRadixString(16).padLeft(6, '0').toUpperCase()}';
38 | var textColor = _theme.textTheme.subtitle1.color;
39 | var textColorHex =
40 | '#${(textColor.value & 0xFFFFFF).toRadixString(16).padLeft(6, 'B').toUpperCase()}';
41 |
42 | return [
43 | new charts.Series(
44 | id: id,
45 | colorFn: (_, __) => __ < data.length - 1
46 | ? charts.Color.fromHex(code: textColorHex)
47 | : charts.Color.fromHex(code: accentHex),
48 | domainFn: (ChartData data, _) => data.xValue,
49 | measureFn: (ChartData data, _) => data.yValue,
50 | data: data,
51 | )
52 | ];
53 | }
54 | }
55 |
56 | /// Sample ordinal data type.
57 | class ChartData {
58 | final String xValue;
59 | final int yValue;
60 |
61 | ChartData(this.xValue, this.yValue);
62 | }
63 |
--------------------------------------------------------------------------------
/lib/widgets/basic/BaseSelectionRadioTile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class BaseSelectionRadioTile extends StatelessWidget {
4 | //
5 | final bool value;
6 | final VoidCallback onTap;
7 | final ValueChanged onChange;
8 | final VoidCallback onClear;
9 | final Icon icon;
10 | final String title;
11 | final String subtitle;
12 |
13 | BaseSelectionRadioTile({
14 | this.value,
15 | this.onTap,
16 | this.onChange,
17 | this.onClear,
18 | this.icon,
19 | this.title,
20 | this.subtitle,
21 | });
22 |
23 | Widget _tile(
24 | Icon _icon,
25 | String title,
26 | String subtitle,
27 | BuildContext context,
28 | ) {
29 | var _theme = Theme.of(context);
30 | var _darkMode = _theme.brightness == Brightness.dark;
31 |
32 | return Container(
33 | decoration: BoxDecoration(
34 | color: _darkMode
35 | ? Colors.grey.withOpacity(0.25)
36 | : Theme.of(context).scaffoldBackgroundColor,
37 | border: Border(
38 | top: BorderSide(color: Colors.grey.withOpacity(0.5), width: 0.5),
39 | bottom: BorderSide(color: Colors.grey.withOpacity(0.5), width: 0.5),
40 | ),
41 | ),
42 | child: ListTile(
43 | leading: _icon,
44 | title: Text('$title'),
45 | subtitle: subtitle == null ? null : Text(subtitle),
46 | trailing: title == null || title == ''
47 | ? Text('')
48 | : IconButton(
49 | color: Theme.of(context).accentColor,
50 | icon: Icon(value ? Icons.add_circle_outline : Icons.cancel),
51 | onPressed: () => this.onChange(!this.value),
52 | ),
53 | onTap: () => this.onTap(),
54 | ),
55 | );
56 | }
57 |
58 | @override
59 | Widget build(BuildContext context) {
60 | return _tile(
61 | this.icon,
62 | this.title,
63 | this.subtitle,
64 | context,
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/lib/widgets/basic/BaseSelection2LineTile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class BaseSelection2LineTile extends StatelessWidget {
4 | //
5 | final String value;
6 | final VoidCallback onTap;
7 | final VoidCallback onClear;
8 | final Icon icon;
9 | final String title;
10 | final String subtitle;
11 | final String emptyText;
12 |
13 | BaseSelection2LineTile({
14 | this.value,
15 | this.onTap,
16 | this.onClear,
17 | this.icon,
18 | this.title,
19 | this.subtitle,
20 | this.emptyText,
21 | });
22 |
23 | Widget _tile(
24 | Icon _icon,
25 | String title,
26 | String subtitle,
27 | BuildContext context,
28 | ) {
29 | var _theme = Theme.of(context);
30 | var _darkMode = _theme.brightness == Brightness.dark;
31 |
32 | return Container(
33 | decoration: BoxDecoration(
34 | color: _darkMode
35 | ? Colors.grey.withOpacity(0.25)
36 | : Theme.of(context).scaffoldBackgroundColor,
37 | border: Border(
38 | top: BorderSide(color: Colors.grey.withOpacity(0.5), width: 0.5),
39 | bottom: BorderSide(color: Colors.grey.withOpacity(0.5), width: 0.5),
40 | ),
41 | ),
42 | child: ListTile(
43 | leading: _icon,
44 | title: Text(title),
45 | subtitle: Text(
46 | subtitle == null || subtitle == '' ? this.emptyText : subtitle,
47 | ),
48 | trailing: subtitle == null || subtitle == ''
49 | ? Text('')
50 | : IconButton(
51 | color: Theme.of(context).accentColor,
52 | icon: Icon(Icons.cancel),
53 | onPressed: () => this.onClear(),
54 | ),
55 | onTap: () => this.onTap(),
56 | ),
57 | );
58 | }
59 |
60 | @override
61 | Widget build(BuildContext context) {
62 | return _tile(
63 | this.icon,
64 | this.title,
65 | this.value,
66 | context,
67 | );
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/lib/pages/ProgressMain.dart:
--------------------------------------------------------------------------------
1 | import 'package:CleanHabits/widgets/basic/BottomNavBar.dart';
2 | import 'package:CleanHabits/widgets/progress/CompletionRate.dart';
3 | import 'package:CleanHabits/widgets/progress/DailyTracker.dart';
4 | import 'package:CleanHabits/widgets/progress/DayWisePerfomance.dart';
5 | import 'package:CleanHabits/widgets/progress/StatusSummary.dart';
6 | import 'package:CleanHabits/widgets/progress/WeeklyProgress.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
9 |
10 | class ProgressMain extends StatelessWidget {
11 | AppBar _appBar(context) {
12 | return new AppBar(
13 | automaticallyImplyLeading: false,
14 | title: Text(
15 | 'Progress',
16 | style: TextStyle(
17 | color: Theme.of(context).textTheme.headline6.color,
18 | ),
19 | ),
20 | elevation: 0.0,
21 | backgroundColor: Theme.of(context).scaffoldBackgroundColor,
22 | );
23 | }
24 |
25 | @override
26 | Widget build(BuildContext context) {
27 | var _selectedNavIndex = 1;
28 |
29 | var _widgetList = [
30 | StatusSummary(),
31 | WeeklyProgress(),
32 | CompletionRate(),
33 | DayWisePerfomance(),
34 | DailyTracker(),
35 | ];
36 |
37 | return new Scaffold(
38 | appBar: _appBar(context),
39 | body: Container(
40 | color: Colors.grey.withOpacity(0.05),
41 | child: CustomScrollView(
42 | slivers: [
43 | SliverAnimatedList(
44 | initialItemCount: _widgetList.length,
45 | itemBuilder: (context, index, anim) =>
46 | AnimationConfiguration.staggeredList(
47 | position: index,
48 | duration: const Duration(milliseconds: 1000),
49 | child: FadeInAnimation(child: _widgetList[index]),
50 | ),
51 | ),
52 | ],
53 | ),
54 | ),
55 | bottomNavigationBar: BottomNavBar(index: _selectedNavIndex),
56 | );
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/lib/widgets/basic/HorizontalBarChart.dart:
--------------------------------------------------------------------------------
1 | import 'package:CleanHabits/widgets/basic/BarChart.dart';
2 | import 'package:charts_flutter/flutter.dart' as charts;
3 | import 'package:flutter/material.dart';
4 | import 'package:intl/intl.dart';
5 |
6 | class HorizontalBarChart extends StatelessWidget {
7 | final List seriesList;
8 | final bool animate;
9 |
10 | HorizontalBarChart(this.seriesList, {this.animate});
11 |
12 | factory HorizontalBarChart.withData(
13 | String id,
14 | List data,
15 | BuildContext context,
16 | ) {
17 | return new HorizontalBarChart(
18 | _createData(id, data, context),
19 | animate: true,
20 | );
21 | }
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | return new charts.BarChart(
26 | seriesList,
27 | animate: animate,
28 | vertical: false,
29 | defaultRenderer: new charts.BarRendererConfig(
30 | cornerStrategy: const charts.ConstCornerStrategy(12),
31 | ),
32 | );
33 | }
34 |
35 | static List> _createData(
36 | id,
37 | List data,
38 | BuildContext context,
39 | ) {
40 | var _theme = Theme.of(context);
41 | var accentColor = _theme.accentColor;
42 | var accentHex =
43 | '#${(accentColor.value & 0xFFFFFF).toRadixString(16).padLeft(6, '0').toUpperCase()}';
44 | var textColor = _theme.textTheme.subtitle1.color;
45 | var textColorHex =
46 | '#${(textColor.value & 0xFFFFFF).toRadixString(16).padLeft(6, 'B').toUpperCase()}';
47 |
48 | var currentDay = DateFormat('EEE').format(DateTime.now());
49 |
50 | return [
51 | new charts.Series(
52 | id: id,
53 | fillColorFn: (_, __) => data[__].xValue == currentDay
54 | ? charts.Color.fromHex(code: accentHex)
55 | : charts.Color.fromHex(code: textColorHex),
56 | domainFn: (ChartData data, _) => data.xValue,
57 | measureFn: (ChartData data, _) => data.yValue,
58 | data: data,
59 | )
60 | ];
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/lib/data/provider/WorkManagerProvider.dart:
--------------------------------------------------------------------------------
1 | import 'package:CleanHabits/data/WorkManagerService.dart';
2 | import 'package:CleanHabits/data/provider/ProviderFactory.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:workmanager/workmanager.dart';
5 |
6 | void callbackDispatcher() {
7 | Workmanager.executeTask((taskName, inputData) async {
8 | //
9 | WidgetsFlutterBinding.ensureInitialized();
10 | await ProviderFactory.initDB();
11 | await ProviderFactory.settingsProvider.init();
12 |
13 | var wms = WorkManagerService();
14 | await wms.callback(taskName, inputData);
15 |
16 | return Future.value(true);
17 | });
18 | }
19 |
20 | class WorkManagerProvider {
21 | Future init() async {
22 | return await Workmanager.initialize(
23 | callbackDispatcher,
24 | isInDebugMode: false,
25 | );
26 | }
27 |
28 | Future runSingleJob({
29 | String id,
30 | String taskName,
31 | Map inputData,
32 | }) async {
33 | //
34 | return await Workmanager.registerOneOffTask(
35 | id,
36 | taskName,
37 | inputData: inputData,
38 | );
39 | }
40 |
41 | Future runSingleJobAt({
42 | String id,
43 | String taskName,
44 | Map inputData,
45 | DateTime then,
46 | }) async {
47 | return await Workmanager.registerOneOffTask(
48 | id,
49 | taskName,
50 | inputData: inputData,
51 | initialDelay: then.difference(DateTime.now()),
52 | );
53 | }
54 |
55 | Future runPeriodicJob({
56 | String id,
57 | String taskName,
58 | Map inputData,
59 | DateTime then,
60 | Duration frequency,
61 | }) async {
62 | return await Workmanager.registerPeriodicTask(
63 | id,
64 | taskName,
65 | frequency: frequency,
66 | inputData: inputData,
67 | initialDelay: then.difference(DateTime.now()),
68 | );
69 | }
70 |
71 | Future cancelJob({String taskName}) async {
72 | return await Workmanager.cancelByUniqueName(taskName);
73 | }
74 |
75 | Future cancelAllJobs() async {
76 | return await Workmanager.cancelAll();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/lib/widgets/basic/StackedBarChart.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | class StackedBarChart extends StatelessWidget {
6 | final List data;
7 |
8 | StackedBarChart({this.data});
9 |
10 | Widget _getBox(BuildContext context, int maxValue, StackData sData) {
11 | var _theme = Theme.of(context);
12 | var accentColor = _theme.accentColor;
13 | var _textStyle = TextStyle(color: _theme.scaffoldBackgroundColor);
14 | var maxWidth = MediaQuery.of(context).size.width;
15 | var thisWidth = ((sData.yValue / maxValue) * maxWidth * 0.65).toDouble();
16 | //
17 | return Container(
18 | padding: EdgeInsets.all(5),
19 | margin: EdgeInsets.fromLTRB(10.0, 5, 10.0, 5.0),
20 | width: thisWidth,
21 | decoration: BoxDecoration(
22 | color: accentColor.withOpacity(0.75),
23 | border: Border.all(width: 3.0, color: accentColor),
24 | borderRadius: BorderRadius.all(Radius.circular(10)),
25 | ),
26 | child: Center(child: Text('${sData.yValue}', style: _textStyle)),
27 | );
28 | }
29 |
30 | @override
31 | Widget build(BuildContext context) {
32 | var maxValue = this.data.map((e) => e.yValue).reduce(max);
33 | //
34 | return new Column(
35 | children: this
36 | .data
37 | .map(
38 | (e) => Row(
39 | crossAxisAlignment: CrossAxisAlignment.center,
40 | mainAxisAlignment: MainAxisAlignment.center,
41 | children: [
42 | Text(e.xStart),
43 | Container(
44 | child: _getBox(context, maxValue, e),
45 | ),
46 | Text(e.xEnd),
47 | ],
48 | ),
49 | // ListTile(
50 | // dense: true,
51 | // leading: Text(e.xStart),
52 | // title: _getBox(context, maxValue, e),
53 | // trailing: Text(e.xEnd),
54 | // ),
55 | )
56 | .toList(),
57 | );
58 | }
59 | }
60 |
61 | class StackData {
62 | final String xStart;
63 | final String xEnd;
64 | final int yValue;
65 |
66 | StackData(this.xStart, this.xEnd, this.yValue);
67 | }
68 |
--------------------------------------------------------------------------------
/lib/widgets/basic/LineChart.dart:
--------------------------------------------------------------------------------
1 | import 'package:charts_flutter/flutter.dart' as charts;
2 | import 'package:flutter/material.dart';
3 |
4 | class LineChart extends StatelessWidget {
5 | final List seriesList;
6 | final bool animate;
7 |
8 | LineChart(this.seriesList, {this.animate});
9 |
10 | factory LineChart.withData(
11 | String id,
12 | List data,
13 | BuildContext context,
14 | ) {
15 | return new LineChart(
16 | _createData(id, data, context),
17 | animate: true,
18 | );
19 | }
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return new charts.LineChart(seriesList,
24 | animate: animate,
25 | defaultRenderer: new charts.LineRendererConfig(
26 | includePoints: true,
27 | includeArea: true,
28 | stacked: true,
29 | ),
30 | domainAxis: new charts.NumericAxisSpec(
31 | tickProviderSpec: new charts.BasicNumericTickProviderSpec(
32 | zeroBound: false,
33 | dataIsInWholeNumbers: false,
34 | desiredTickCount: seriesList[0].data.length,
35 | ),
36 | tickFormatterSpec: charts.BasicNumericTickFormatterSpec(
37 | (num value) =>
38 | (seriesList[0].data[value.toInt() - 1] as LinearData).xLabel,
39 | ),
40 | ));
41 | }
42 |
43 | static List> _createData(
44 | id,
45 | List data,
46 | BuildContext context,
47 | ) {
48 | var _theme = Theme.of(context);
49 | var accentColor = _theme.accentColor;
50 | var accentHex =
51 | '#${(accentColor.value & 0xFFFFFF).toRadixString(16).padLeft(6, '0').toUpperCase()}';
52 |
53 | return [
54 | new charts.Series(
55 | id: id,
56 | colorFn: (_, __) => charts.Color.fromHex(code: accentHex),
57 | domainFn: (LinearData data, _) => data.xValue,
58 | measureFn: (LinearData data, _) => data.yValue,
59 | data: data,
60 | )
61 | ];
62 | }
63 | }
64 |
65 | class LinearData {
66 | final String xLabel;
67 | final int xValue;
68 | final int yValue;
69 |
70 | LinearData(this.xLabel, this.xValue, this.yValue);
71 | }
72 |
--------------------------------------------------------------------------------
/lib/data/provider/ServiceLastRunProvider.dart:
--------------------------------------------------------------------------------
1 | import 'package:sqflite/sqflite.dart';
2 | import 'package:sqflite/sqlite_api.dart';
3 | import 'package:CleanHabits/data/domain/ServiceLastRun.dart';
4 |
5 | class ServiceLastRunProvider {
6 | Database db;
7 |
8 | Future open(String path) async {
9 | //
10 | db = await openDatabase(
11 | path,
12 | version: 1,
13 | onCreate: (Database db, int version) async {
14 | await db.execute('''
15 | create table $tableServiceLastRun (
16 | $columnId integer primary key autoincrement,
17 | $columnLastUpdated integer not null
18 | )
19 | ''');
20 | },
21 | );
22 | }
23 |
24 | Future insert(ServiceLastRun runData) async {
25 | runData.id = await db.insert(
26 | tableServiceLastRun,
27 | runData.toMap(),
28 | );
29 | return runData;
30 | }
31 |
32 | Future getData(int id) async {
33 | List