diff --git a/.travis.yml b/.travis.yml index 4122bd1e..0ca396e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,8 @@ git: depth: false android: components: - - build-tools-29.0.2 - - android-29 + - build-tools-31.0.0 + - android-31 script: - "./gradlew assembleProductionRelease" deploy: diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..907f5eeb --- /dev/null +++ b/LICENSE @@ -0,0 +1,189 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + Copyright 2021 7LPdWcaW + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index 65a5e402..67eddd26 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,32 @@ # GrowTracker +[![Latest alpha](https://travis-ci.com/7LPdWcaW/GrowTracker-Android.svg?branch=alpha)](https://travis-ci.com/7LPdWcaW/GrowTracker-Android) +[![GitHub commits since latest release](https://img.shields.io/github/commits-since/7LPdWcaW/GrowTracker-Android/latest)](https://github.com/7LPdWcaW/GrowTracker-Android/releases/tag/latest) +[![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/growutils?color=orange)](https://reddit.com/r/growutils) +[![GitHub license](https://img.shields.io/github/license/7LPdWcaW/GrowTracker-Android?color=lightgrey)](https://github.com/7LPdWcaW/GrowTracker-Android/blob/master/LICENSE) + Welcome to grow tracker. This is a utility app designed for gardening and tracking various parameters of your grow. -[![Latest Nightly](https://travis-ci.com/7LPdWcaW/GrowTracker-Android.svg?branch=alpha)](https://travis-ci.com/7LPdWcaW/GrowTracker-Android) +# Discontinuation -[Latest Nightly Build (Experimental!)](https://github.com/7LPdWcaW/GrowTracker-Android/releases/tag/alpha) +As of 2020, major version 2 of the app is in maintenance mode, meaning only critical bugs will be fixed. All further development is reserved for **major version 3** of the application which will eventually replace this. [Read more here](https://github.com/7LPdWcaW/GrowTracker-Android/issues/206) -[Latest APK: (SHA256) 501786b7350eceb7b894a5745c06c378f1d2f2e6f4bf659ee2576b3dfaca5732 v2.6.1](https://github.com/7LPdWcaW/GrowTracker-Android/releases/download/v2.6.1/v2.6.1-production.apk) +# Install -[Latest APK (English only): (SHA256) e366c67c54548da4c46206c953e8847ba6e4c933449ca8d33525601ee2d87bb8 v2.6.1](https://github.com/7LPdWcaW/GrowTracker-Android/releases/download/v2.6.1/v2.6.1-en.apk) +[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/7LPdWcaW/GrowTracker-Android?label=latest%20version&sort=semver)](https://github.com/7LPdWcaW/GrowTracker-Android/releases) +[![F-Droid](https://img.shields.io/f-droid/v/me.anon.grow)](https://f-droid.org/en/packages/me.anon.grow/) -[Latest APK (Discrete): (SHA256) 3b5edaceb462c6fcd51d11652943357976f75b53dacdfe650f422933357688d9 v2.6.1](https://github.com/7LPdWcaW/GrowTracker-Android/releases/download/v2.6.1/v2.6.1-discrete.apk) +[Get it on F-Droid](https://f-droid.org/en/packages/me.anon.grow/) -[Get it on F-Droid with automatic updates](https://f-droid.org/packages/me.anon.grow/) +The app requires no permissions except for external storage (for caching plant data and images) which you can see [here](https://github.com/7LPdWcaW/GrowTracker-Android/blob/develop/app/src/main/AndroidManifest.xml) in order for users to maintain anonymity, and a minimum Android version of `4.2` and above -You can follow development, post questions, or grow logs in the [Subreddit](https://reddit.com/r/growutils) +- [Latest Nightly Build (Experimental!)](https://github.com/7LPdWcaW/GrowTracker-Android/releases/tag/alpha) -# Installation +- [Latest APK: (SHA256) 893ce94c1d7da17c57869273e4c74ee3bb9ea5d6fae306bb7f96feb785601ef4 v2.6.3](https://github.com/7LPdWcaW/GrowTracker-Android/releases/download/v2.6.3/v2.6.3-production.apk) -The app requires no permissions except for external storage (for caching plant data and images) which you can see [here](https://github.com/7LPdWcaW/GrowTracker-Android/blob/develop/app/src/main/AndroidManifest.xml) in order for users to maintain anonymity, and a minimum Android version of `4.2` and above +- [Latest APK (English only): (SHA256) 44242d3f022ea25549d0f0d4c5d04bbe659901098d5fe96b63583a33dd5f29d0 v2.6.3](https://github.com/7LPdWcaW/GrowTracker-Android/releases/download/v2.6.3/v2.6.3-en.apk) + +- [Latest APK (Discrete): (SHA256) f8a73f83bff3b0dc00d8b4bf5670a6e43d1a37fed2d7c506e2033c619fab8012 v2.6.3](https://github.com/7LPdWcaW/GrowTracker-Android/releases/download/v2.6.3/v2.6.3-discrete.apk) ## How to install from APK @@ -28,12 +36,21 @@ The app requires no permissions except for external storage (for caching plant d ## Updating -You can either elect to update manually, or get notified on releases by installing the [Update plugin](https://github.com/7LPdWcaW/GrowUpdater-Android/releases) +You can either elect to update manually, or get notified on releases by installing the [Update plugin](https://github.com/7LPdWcaW/GrowUpdater-Android/releases). **For updates, do not uninstall first, you will lose your existing plant data. Always back up your data!** +Installing the app via F-Droid makes it updateable through the F-Droid mechanism. + # Screenshots +[![main plant list](fastlane/metadata/android/en-GB/images/phoneScreenshotsThumbs/1.png)](fastlane/metadata/android/en-GB/images/phoneScreenshots/1.png) +[![dark main plant list](fastlane/metadata/android/en-GB/images/phoneScreenshotsThumbs/1b.png)](fastlane/metadata/android/en-GB/images/phoneScreenshots/1b.png) + + +
+ More screenshots + [![install](fastlane/metadata/android/en-GB/images/phoneScreenshotsThumbs/install.png)](fastlane/metadata/android/en-GB/images/phoneScreenshots/install.png) [![main plant list](fastlane/metadata/android/en-GB/images/phoneScreenshotsThumbs/1.png)](fastlane/metadata/android/en-GB/images/phoneScreenshots/1.png) [![plant details](fastlane/metadata/android/en-GB/images/phoneScreenshotsThumbs/2.png)](fastlane/metadata/android/en-GB/images/phoneScreenshots/2.png) @@ -55,6 +72,8 @@ You can either elect to update manually, or get notified on releases by installi [![dark garden tracker](fastlane/metadata/android/en-GB/images/phoneScreenshotsThumbs/9b.png)](fastlane/metadata/android/en-GB/images/phoneScreenshots/9b.png) [![settings](fastlane/metadata/android/en-GB/images/phoneScreenshotsThumbs/10.png)](fastlane/metadata/android/en-GB/images/phoneScreenshots/10.png) +
+ # About the app The app was designed with data in mind. All data is easily accessible via the app's files folder in `Android/data/me.anon.grow/files/`. You will need a file explorer to browse this folder, or alternatively, you can back your data up via the app settings which will create copies in `backups/GrowTracker/` @@ -63,7 +82,12 @@ The structure is very simple, and consists of a few different objects. *Note*: date timestamps are all unix timestamps from 1/1/1970 in milliseconds. All objects in arrays are in date order, where index 0 is the oldest and index (size - 1) is the newest. -## Plant object +## API Data structure + +
+ Expand section + +### Plant object - `plantDate` in milliseconds - `images` is an array of file paths. Image file names are the taken date as unix timestamp in milliseconds @@ -88,7 +112,7 @@ One of, `SOIL`, `HYDRO`, `COCO`, `AERO` -## Actions +### Actions All actions have the following 3 properties @@ -209,7 +233,7 @@ One of, } ``` -## Garden object +### Garden object The garden object is similar to the plant object, and accepts `Action` types, but is software-restricted to the following @@ -264,6 +288,7 @@ The garden object is similar to the plant object, and accepts `Action` types, bu "type": "LightingChange" } ``` +
# Encryption @@ -275,24 +300,27 @@ You can decrypt your files using your passphrase either by writing a script that # Translators +Translating is done conveniently through [Transifex](https://www.transifex.com/growutils/growtracker/) + +See [more](https://github.com/7LPdWcaW/GrowTracker-Android/issues/116) about translating GrowTracker + Translations provided by; -- Alex (Noxmiles) - de ![DE - German](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/de_DE/translated.png) -- Basti B (Weltenesche) - de ![DE - German](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/de_DE/translated.png) -- Heimen Stoffels (Vistaus) - nl ![NL - Dutch](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/nl_NL/translated.png) -- EmmanuelMess - es ![ES - Spanish](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/es/translated.png) -- Maxtille - fr ![FR - French](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/fr/translated.png) -- Patrick B (EukalyptusX) - de ![DE - German](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/de_DE/translated.png) -- Sascha Zenglein (szenglein) - de ![DE - German](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/de_DE/translated.png) -- Vexatos - de ![DE - German](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/de_DE/translated.png) -- W Q (williq) - de ![DE - German](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/de_DE/translated.png) -- 9YbQiuEohUu1 - ru/uk ![UK - Ukrainian](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/uk/translated.png) ![RU - Russian](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/ru/translated.png) +- Chinese (Taiwan) - ![TW - Chinese (Taiwan)](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/zh_TW/translated.png); Chief Ndora (chiefndora), codecyang +- Dutch - ![NL - Dutch](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/nl_NL/translated.png); Heimen Stoffels (Vistaus) +- French - ![FR - French](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/fr/translated.png); Maxtille, yassine azirem (yassix.well) +- German - ![DE - German](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/de_DE/translated.png); Acrylic Boy, Alex (Noxmiles), Basti B (Weltenesche), Patrick B (EukalyptusX), Sascha Zenglein (szenglein), Vexatos, W Q (williq) +- Hungarian - ![HU - Hungarian](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/hu/translated.png); +- Norwegian Bokmål - ![NB - Norwegian Bokmål](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/nb/translated.png); Syver Stensholt (SuperPotato) +- Russian - ![RU - Russian](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/ru/translated.png); 9YbQiuEohUu1 +- Slovenian - ![SI - Slovenian](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/sl_SI/translated.png); Klemen Skerbiš (aha999) +- Spanish - ![ES - Spanish](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/es/translated.png); EmmanuelMess, Raul Choque (choqueraul123) +- Ukranian - ![UK - Ukrainian](https://transifex-open-api.herokuapp.com/badge/growutils/project/growtracker/language/uk/translated.png); 9YbQiuEohUu1 -See [more](https://github.com/7LPdWcaW/GrowTracker-Android/issues/116) about translating GrowTracker # License -Copyright 2014-2019 7LPdWcaW +Copyright 2014-2021 7LPdWcaW Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/app/build.gradle b/app/build.gradle index 326212d3..7c8a0d26 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,16 +22,17 @@ def getCommitCountTotal = { -> } android { - compileSdkVersion 29 - buildToolsVersion "29.0.2" + compileSdkVersion 31 + buildToolsVersion "31.0.0" defaultConfig { applicationId "me.anon.grow" minSdkVersion 17 - targetSdkVersion 29 - versionCode 1370//getCommitCountTotal() - versionName "2.6.1" + targetSdkVersion 28 + versionCode 2630//getCommitCountTotal() + versionName "2.6.3" versionNameSuffix (travis ? "-alpha" : "") + multiDexEnabled true compileOptions { sourceCompatibility 1.8 @@ -40,12 +41,6 @@ android { resValue "string", "version_date", System.currentTimeMillis().toString() - javaCompileOptions { - annotationProcessorOptions { - includeCompileClasspath false - } - } - vectorDrawables.useSupportLibrary = true } @@ -112,35 +107,46 @@ android { signingConfig signingConfigs.release } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } } dependencies { - implementation 'androidx.core:core:1.1.0' - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.preference:preference:1.1.0' - implementation 'androidx.recyclerview:recyclerview:1.0.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.multidex:multidex:2.0.1' + implementation 'androidx.core:core:1.3.2' + implementation 'androidx.core:core-ktx:1.3.2' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.preference:preference:1.1.1' + implementation 'androidx.recyclerview:recyclerview:1.2.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.exifinterface:exifinterface:1.0.0' + implementation 'androidx.exifinterface:exifinterface:1.3.2' implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'com.github.prolificinteractive:material-calendarview:2.0.0' - implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1' + implementation 'com.jakewharton.threetenabp:threetenabp:1.2.4' - implementation 'com.google.android.material:material:1.2.0-alpha01' - implementation 'com.google.android:flexbox:1.1.0' + implementation 'com.google.android.material:material:1.4.0-alpha02' + implementation 'com.google.android:flexbox:1.1.0' implementation 'com.esotericsoftware:kryo:3.0.3' - implementation 'com.squareup.moshi:moshi-kotlin:1.8.0' + implementation 'com.squareup.moshi:moshi-kotlin:1.9.2' implementation 'com.squareup:otto:1.3.8' - implementation 'com.github.PhilJay:MPAndroidChart:v2.1.6' +// api 'com.github.PhilJay:MPAndroidChart:v2.1.6' implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.6.0' implementation 'net.lingala.zip4j:zip4j:1.3.2' - api "org.jetbrains.kotlin:kotlin-reflect:1.3.41" - api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41" + implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' + + api "org.jetbrains.kotlin:kotlin-reflect:1.5.20" + api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.20" - kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.8.0' + kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.12.0' } androidExtensions { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5193da2d..7f8f5ee0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -66,7 +66,7 @@ - + diff --git a/app/src/main/java/me/anon/controller/adapter/ActionAdapter.java b/app/src/main/java/me/anon/controller/adapter/ActionAdapter.java index 82246251..2a0ca289 100644 --- a/app/src/main/java/me/anon/controller/adapter/ActionAdapter.java +++ b/app/src/main/java/me/anon/controller/adapter/ActionAdapter.java @@ -15,6 +15,11 @@ import com.esotericsoftware.kryo.Kryo; import com.prolificinteractive.materialcalendarview.CalendarDay; +import com.prolificinteractive.materialcalendarview.DayViewDecorator; +import com.prolificinteractive.materialcalendarview.DayViewFacade; +import com.prolificinteractive.materialcalendarview.MaterialCalendarView; +import com.prolificinteractive.materialcalendarview.OnDateSelectedListener; +import com.prolificinteractive.materialcalendarview.spans.DotSpan; import org.jetbrains.annotations.NotNull; import org.threeten.bp.Instant; @@ -75,6 +80,7 @@ public interface OnItemSelectCallback public void onItemSelected(Action action); } + private OnDateSelectedListener onDateSelectedListener; private OnItemSelectCallback onItemSelectCallback; private OnActionSelectListener onActionSelectListener; @Nullable private Plant plant; @@ -83,6 +89,7 @@ public interface OnItemSelectCallback private TempUnit tempUnit; private boolean showDate = true; private boolean showActions = true; + public boolean showCalendar = false; private CalendarDay selectedFilterDate = null; public void setFilterDate(CalendarDay selectedFilterDate) @@ -90,6 +97,11 @@ public void setFilterDate(CalendarDay selectedFilterDate) this.selectedFilterDate = selectedFilterDate; } + public void setOnDateChangedListener(OnDateSelectedListener onDateSelectedListener) + { + this.onDateSelectedListener = onDateSelectedListener; + } + /** * Dummy image action placeholder class */ @@ -105,6 +117,7 @@ private static class ImageAction extends Action implements Parcelable @Override public long getDate() { + if (images.size() <= 0) return 0; return getImageDate(images.get(0)); } @@ -268,6 +281,13 @@ private static long getImageDate(String image) @Override public int getItemViewType(int position) { + if (showCalendar && position == 0) + { + return 3; + } + + position = position - (showCalendar ? 1 : 0); + if (selectedFilterDate != null) { LocalDate actionDate = CalendarDay.from(LocalDate.from(Instant.ofEpochMilli(actions.get(position).getDate()).atZone(ZoneId.systemDefault()))).getDate(); @@ -284,7 +304,11 @@ private static long getImageDate(String image) @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { - if (viewType == 0) + if (viewType == 3) + { + return new RecyclerView.ViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.calendar_item, viewGroup, false)){}; + } + else if (viewType == 0) { return new RecyclerView.ViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.empty, viewGroup, false)){}; } @@ -300,7 +324,41 @@ else if (viewType == 2) { if (getItemViewType(index) == 0) return; - final Action action = actions.get(index); + if (getItemViewType(index) == 3) + { + final MaterialCalendarView calendar = (MaterialCalendarView)vh.itemView; + calendar.removeDecorators(); + calendar.addDecorator(new DayViewDecorator() + { + @Override public boolean shouldDecorate(CalendarDay calendarDay) + { + // find an action that is on this day + for (Action action : plant.getActions()) + { + LocalDate actionDate = CalendarDay.from(LocalDate.from(Instant.ofEpochMilli(action.getDate()).atZone(ZoneId.systemDefault()))).getDate(); + if (calendarDay.getDate().equals(actionDate)) + { + return true; + } + } + + return false; + } + + @Override public void decorate(DayViewFacade dayViewFacade) + { + dayViewFacade.addSpan(new DotSpan(6.0f, IntUtilsKt.resolveColor(R.attr.colorAccent, calendar.getContext()))); + } + }); + calendar.setOnDateChangedListener(onDateSelectedListener); + calendar.setSelectedDate(selectedFilterDate); + calendar.setCurrentDate(selectedFilterDate); + + return; + } + + final int actionIndex = index - (showCalendar ? 1 : 0); + final Action action = actions.get(actionIndex); DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(vh.itemView.getContext()); DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(vh.itemView.getContext()); TextView dateDay = null; @@ -343,9 +401,9 @@ else if (vh instanceof ActionHolder) String fullDateStr = dateFormat.format(actionDate) + " " + timeFormat.format(actionDate); String dateStr = vh.itemView.getContext().getString(R.string.ago, "" + new DateRenderer(viewHolder.itemView.getContext()).timeAgo(action.getDate()).formattedDate + ""); - if (index > 0) + if (actionIndex > 0) { - long difference = actions.get(index - 1).getDate() - action.getDate(); + long difference = actions.get(actionIndex - 1).getDate() - action.getDate(); int days = (int)Math.round(((double)difference / 60d / 60d / 24d / 1000d)); dateStr += " (-" + days + vh.itemView.getContext().getString(R.string.day_abbr) + ")"; @@ -486,9 +544,9 @@ else if (item.getItemId() == R.id.delete) { String lastDateStr = ""; - if (index - 1 >= 0) + if (actionIndex - 1 >= 0) { - Date lastActionDate = new Date(actions.get(index - 1).getDate()); + Date lastActionDate = new Date(actions.get(actionIndex - 1).getDate()); Calendar lastActionCalendar = GregorianCalendar.getInstance(); lastActionCalendar.setTime(lastActionDate); lastDateStr = lastActionCalendar.get(Calendar.DAY_OF_MONTH) + " " + lastActionCalendar.getDisplayName(Calendar.MONTH, Calendar.SHORT, Locale.getDefault()); @@ -503,13 +561,13 @@ else if (item.getItemId() == R.id.delete) StageChange lastChange = null; long currentChangeDate = action.getDate(); - for (int actionIndex = index; actionIndex < actions.size(); actionIndex++) + for (int aIndex = actionIndex; aIndex < actions.size(); aIndex++) { - if (actions.get(actionIndex) instanceof StageChange) + if (actions.get(aIndex) instanceof StageChange) { if (lastChange == null) { - lastChange = (StageChange)actions.get(actionIndex); + lastChange = (StageChange)actions.get(aIndex); break; } } @@ -564,7 +622,7 @@ else if (item.getItemId() == R.id.delete) @Override public int getItemCount() { - return actions.size(); + return actions.size() + (showCalendar ? 1 : 0); } @Override public void onItemMove(int fromPosition, int toPosition) diff --git a/app/src/main/java/me/anon/controller/adapter/FeedingDateAdapter.kt b/app/src/main/java/me/anon/controller/adapter/FeedingDateAdapter.kt index e8f9e95a..12b753a1 100644 --- a/app/src/main/java/me/anon/controller/adapter/FeedingDateAdapter.kt +++ b/app/src/main/java/me/anon/controller/adapter/FeedingDateAdapter.kt @@ -9,6 +9,7 @@ import me.anon.model.Plant import me.anon.model.PlantStage import me.anon.view.FeedingDateHolder import java.util.* +import kotlin.collections.ArrayList /** * // TODO: Add class description @@ -23,10 +24,17 @@ class FeedingDateAdapter : RecyclerView.Adapter() items.addAll(value) notifyDataSetChanged() } - public var plant: Plant = Plant() - public val plantStages: SortedMap by lazy { plant.calculateStageTime() } + public var plants: ArrayList = arrayListOf() + public val plantStages: ArrayList> by lazy { + ArrayList(plants.map { it.calculateStageTime() }) + } - public fun getLastStage(): PlantStage = plantStages.toSortedMap().lastKey() + public fun getLastStages(): ArrayList + { + return ArrayList(plantStages.map { + it.toSortedMap().lastKey() + }) + } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedingDateHolder = FeedingDateHolder(this, LayoutInflater.from(parent.context).inflate(R.layout.feeding_date_stub, parent, false)) diff --git a/app/src/main/java/me/anon/controller/receiver/BackupService.kt b/app/src/main/java/me/anon/controller/receiver/BackupService.kt index cc8d709b..20df62a0 100644 --- a/app/src/main/java/me/anon/controller/receiver/BackupService.kt +++ b/app/src/main/java/me/anon/controller/receiver/BackupService.kt @@ -13,16 +13,24 @@ class BackupService : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - var backup = true - File(BackupHelper.FILES_PATH).listFiles()?.let { - val sorted = ArrayList(it.sortedBy { it.lastModified() }) - val today = LocalDate.now() - backup = DateTimeUtils.toLocalDate(Date(sorted.last().lastModified())) != today - } + try + { + var backup = true + val files = File(BackupHelper.FILES_PATH).listFiles() + if (files != null && files.isNotEmpty()) + { + val sorted = ArrayList(files.sortedBy { it.lastModified() }) + val today = LocalDate.now() + backup = DateTimeUtils.toLocalDate(Date(sorted.last().lastModified())) != today + } - if (backup) + if (backup) + { + BackupHelper.backupJson() + } + } + catch (e: Exception) { - BackupHelper.backupJson() } } } diff --git a/app/src/main/java/me/anon/grow/ActionsActivity.kt b/app/src/main/java/me/anon/grow/ActionsActivity.kt index 2483cf25..f6ebbb06 100644 --- a/app/src/main/java/me/anon/grow/ActionsActivity.kt +++ b/app/src/main/java/me/anon/grow/ActionsActivity.kt @@ -22,7 +22,7 @@ class ActionsActivity : BaseActivity() if (supportFragmentManager.findFragmentByTag("fragment") == null) { supportFragmentManager.beginTransaction() - .replace(R.id.coordinator, ActionsListFragment.newInstance(intent.extras), "fragment") + .replace(R.id.fragment_holder, ActionsListFragment.newInstance(intent.extras), "fragment") .commit() } } diff --git a/app/src/main/java/me/anon/grow/AddWateringActivity.kt b/app/src/main/java/me/anon/grow/AddWateringActivity.kt index 8aa07508..15227f80 100644 --- a/app/src/main/java/me/anon/grow/AddWateringActivity.kt +++ b/app/src/main/java/me/anon/grow/AddWateringActivity.kt @@ -15,6 +15,7 @@ class AddWateringActivity : BaseActivity() setSupportActionBar(toolbar) var plantIndex: IntArray? = intent.extras?.getIntArray("plant_index") ?: intArrayOf(-1) + var gardenIndex: Int = intent.extras?.getInt("garden_index") ?: -1 if (plantIndex == null || plantIndex.size == 0) { @@ -24,7 +25,7 @@ class AddWateringActivity : BaseActivity() if (supportFragmentManager.findFragmentByTag(TAG_FRAGMENT) == null) { - supportFragmentManager.beginTransaction().replace(R.id.coordinator, WateringFragment.newInstance(plantIndex, -1), TAG_FRAGMENT).commit() + supportFragmentManager.beginTransaction().replace(R.id.fragment_holder, WateringFragment.newInstance(plantIndex, -1, gardenIndex), TAG_FRAGMENT).commit() } } diff --git a/app/src/main/java/me/anon/grow/EditWateringActivity.kt b/app/src/main/java/me/anon/grow/EditWateringActivity.kt index cb14639d..db8090db 100644 --- a/app/src/main/java/me/anon/grow/EditWateringActivity.kt +++ b/app/src/main/java/me/anon/grow/EditWateringActivity.kt @@ -38,7 +38,7 @@ class EditWateringActivity : BaseActivity() if (supportFragmentManager.findFragmentByTag("fragment") == null) { supportFragmentManager.beginTransaction() - .replace(R.id.coordinator, WateringFragment.newInstance(intArrayOf(plantIndex), feedingIndex), "fragment") + .replace(R.id.fragment_holder, WateringFragment.newInstance(intArrayOf(plantIndex), feedingIndex, -1), "fragment") .commit() } } diff --git a/app/src/main/java/me/anon/grow/FeedingScheduleActivity.kt b/app/src/main/java/me/anon/grow/FeedingScheduleActivity.kt index 34beb918..db581dda 100644 --- a/app/src/main/java/me/anon/grow/FeedingScheduleActivity.kt +++ b/app/src/main/java/me/anon/grow/FeedingScheduleActivity.kt @@ -24,7 +24,7 @@ class FeedingScheduleActivity : BaseActivity() if (supportFragmentManager.findFragmentByTag(TAG_FRAGMENT) == null) { - supportFragmentManager.beginTransaction().replace(R.id.coordinator, FeedingScheduleListFragment(), TAG_FRAGMENT).commit() + supportFragmentManager.beginTransaction().replace(R.id.fragment_holder, FeedingScheduleListFragment(), TAG_FRAGMENT).commit() } } } diff --git a/app/src/main/java/me/anon/grow/FeedingScheduleDetailsActivity.kt b/app/src/main/java/me/anon/grow/FeedingScheduleDetailsActivity.kt index ebda56de..213a3fcf 100644 --- a/app/src/main/java/me/anon/grow/FeedingScheduleDetailsActivity.kt +++ b/app/src/main/java/me/anon/grow/FeedingScheduleDetailsActivity.kt @@ -24,7 +24,7 @@ class FeedingScheduleDetailsActivity : BaseActivity() if (supportFragmentManager.findFragmentByTag(TAG_FRAGMENT) == null) { - supportFragmentManager.beginTransaction().replace(R.id.coordinator, FeedingScheduleDetailsFragment.newInstance(intent.extras), TAG_FRAGMENT).commit() + supportFragmentManager.beginTransaction().replace(R.id.fragment_holder, FeedingScheduleDetailsFragment.newInstance(intent.extras), TAG_FRAGMENT).commit() } } diff --git a/app/src/main/java/me/anon/grow/MainActivity.java b/app/src/main/java/me/anon/grow/MainActivity.java index e9bd89e0..abd01165 100644 --- a/app/src/main/java/me/anon/grow/MainActivity.java +++ b/app/src/main/java/me/anon/grow/MainActivity.java @@ -123,13 +123,13 @@ public NavigationView getNavigation() { if (navigation.getMenu().findItem(selectedItem).isCheckable()) { - getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.coordinator)).commit(); + getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.fragment_holder)).commit(); navigation.getMenu().findItem(selectedItem).setChecked(true); onNavigationItemSelected(navigation.getMenu().findItem(selectedItem)); } } - getSupportFragmentManager().findFragmentById(R.id.coordinator).onActivityResult(requestCode, resultCode, data); + getSupportFragmentManager().findFragmentById(R.id.fragment_holder).onActivityResult(requestCode, resultCode, data); } @Override protected void onResume() @@ -334,7 +334,7 @@ else if (item.getItemId() == R.id.all) navigation.getMenu().findItem(R.id.garden_menu).getSubMenu().findItem(R.id.all).setChecked(true); selectedItem = item.getItemId(); - getSupportFragmentManager().beginTransaction().replace(R.id.coordinator, PlantListFragment.newInstance(), TAG_FRAGMENT).commit(); + getSupportFragmentManager().beginTransaction().replace(R.id.fragment_holder, PlantListFragment.newInstance(), TAG_FRAGMENT).commit(); } else if (item.getItemId() >= 100 && item.getItemId() < Integer.MAX_VALUE) { @@ -349,7 +349,7 @@ else if (item.getItemId() >= 100 && item.getItemId() < Integer.MAX_VALUE) selectedItem = item.getItemId(); item.setChecked(true); int gardenIndex = item.getItemId() - 100; - getSupportFragmentManager().beginTransaction().replace(R.id.coordinator, GardenHostFragment.newInstance(GardenManager.getInstance().getGardens().get(gardenIndex)), TAG_FRAGMENT).commit(); + getSupportFragmentManager().beginTransaction().replace(R.id.fragment_holder, GardenHostFragment.newInstance(GardenManager.getInstance().getGardens().get(gardenIndex)), TAG_FRAGMENT).commit(); } if (drawer != null) diff --git a/app/src/main/java/me/anon/grow/MainApplication.java b/app/src/main/java/me/anon/grow/MainApplication.java index d56d5b30..9344c7ba 100644 --- a/app/src/main/java/me/anon/grow/MainApplication.java +++ b/app/src/main/java/me/anon/grow/MainApplication.java @@ -1,9 +1,7 @@ package me.anon.grow; import android.app.AlarmManager; -import android.app.Application; import android.app.PendingIntent; -import android.app.backup.BackupManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -34,6 +32,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import androidx.multidex.MultiDexApplication; import me.anon.controller.receiver.BackupService; import me.anon.lib.handler.ExceptionHandler; import me.anon.lib.helper.BackupHelper; @@ -44,14 +43,7 @@ import me.anon.lib.stream.DecryptInputStream; import me.anon.lib.stream.EncryptOutputStream; -/** - * // TODO: Add class description - * - * @author 7LPdWcaW - * @documentation // TODO Reference flow doc - * @project GrowTracker - */ -public class MainApplication extends Application +public class MainApplication extends MultiDexApplication { private static DisplayImageOptions displayImageOptions; private static boolean encrypted = false; diff --git a/app/src/main/java/me/anon/grow/PlantDetailsActivity.kt b/app/src/main/java/me/anon/grow/PlantDetailsActivity.kt index 9e653417..f5773599 100644 --- a/app/src/main/java/me/anon/grow/PlantDetailsActivity.kt +++ b/app/src/main/java/me/anon/grow/PlantDetailsActivity.kt @@ -11,8 +11,9 @@ import kotlinx.android.synthetic.main.fragment_holder.toolbar_layout import kotlinx.android.synthetic.main.tabbed_fragment_holder.* import me.anon.grow.fragment.ActionsListFragment import me.anon.grow.fragment.PlantDetailsFragment -import me.anon.grow.fragment.StatisticsFragment +import me.anon.grow.fragment.StatisticsFragment2 import me.anon.grow.fragment.ViewPhotosFragment +import me.anon.lib.manager.PlantManager import me.anon.model.Plant class PlantDetailsActivity : BaseActivity() @@ -44,11 +45,11 @@ class PlantDetailsActivity : BaseActivity() } "statistics" -> { tabs.selectedItemId = R.id.view_statistics - StatisticsFragment.newInstance(intent.extras) + StatisticsFragment2.newInstance(intent.extras!!) } else -> PlantDetailsFragment.newInstance(intent.extras) } - supportFragmentManager.beginTransaction().replace(R.id.coordinator, fragment, TAG_FRAGMENT).commit() + supportFragmentManager.beginTransaction().replace(R.id.fragment_holder, fragment, TAG_FRAGMENT).commit() } tabs.visibility = View.GONE @@ -58,28 +59,32 @@ class PlantDetailsActivity : BaseActivity() tabs.visibility = View.VISIBLE tabs.setOnNavigationItemSelectedListener { plant = intent.extras?.get("plant") as Plant - val fragment = supportFragmentManager.findFragmentById(R.id.coordinator) + val fragment = supportFragmentManager.findFragmentById(R.id.fragment_holder) if (fragment is PlantDetailsFragment) { fragment.save() + plant = PlantManager.instance.getPlant(plant.id)!! + intent.extras?.putParcelable("plant", plant) } toolbarLayout.removeViews(1, toolbarLayout.childCount - 1) supportFragmentManager.beginTransaction() .setCustomAnimations(R.anim.fade_in, R.anim.fade_out) - .replace(R.id.coordinator, when (it.itemId) + .replace(R.id.fragment_holder, when (it.itemId) { R.id.view_details -> PlantDetailsFragment.newInstance(intent.extras) R.id.view_history -> ActionsListFragment.newInstance(intent.extras) R.id.view_photos -> ViewPhotosFragment.newInstance(intent.extras) - R.id.view_statistics -> StatisticsFragment.newInstance(intent.extras) + R.id.view_statistics -> StatisticsFragment2.newInstance(intent.extras!!) else -> Fragment() }, TAG_FRAGMENT) .commit() return@setOnNavigationItemSelectedListener true } + + supportActionBar?.subtitle = plant.name } } } @@ -96,7 +101,7 @@ class PlantDetailsActivity : BaseActivity() { if (item.itemId == android.R.id.home) { - val fragment = supportFragmentManager.findFragmentById(R.id.coordinator) + val fragment = supportFragmentManager.findFragmentById(R.id.fragment_holder) if (fragment is PlantDetailsFragment) { diff --git a/app/src/main/java/me/anon/grow/ScheduleDateDetailsActivity.kt b/app/src/main/java/me/anon/grow/ScheduleDateDetailsActivity.kt index 28603a8a..527d0801 100644 --- a/app/src/main/java/me/anon/grow/ScheduleDateDetailsActivity.kt +++ b/app/src/main/java/me/anon/grow/ScheduleDateDetailsActivity.kt @@ -25,7 +25,7 @@ class ScheduleDateDetailsActivity : BaseActivity() if (supportFragmentManager.findFragmentByTag(TAG_FRAGMENT) == null) { supportFragmentManager.beginTransaction().replace( - R.id.coordinator, + R.id.fragment_holder, ScheduleDateDetailsFragment.newInstance(intent.extras!!), TAG_FRAGMENT ).commit() diff --git a/app/src/main/java/me/anon/grow/SettingsActivity.java b/app/src/main/java/me/anon/grow/SettingsActivity.java index cd687a04..b9a43448 100644 --- a/app/src/main/java/me/anon/grow/SettingsActivity.java +++ b/app/src/main/java/me/anon/grow/SettingsActivity.java @@ -20,7 +20,9 @@ public class SettingsActivity extends BaseActivity if (getSupportFragmentManager().findFragmentByTag(TAG_FRAGMENT) == null) { - getSupportFragmentManager().beginTransaction().replace(R.id.coordinator, new SettingsFragment(), TAG_FRAGMENT).commit(); + getSupportFragmentManager().beginTransaction() + .replace(R.id.fragment_holder, new SettingsFragment(), TAG_FRAGMENT) + .commit(); } } } diff --git a/app/src/main/java/me/anon/grow/StatisticsActivity.kt b/app/src/main/java/me/anon/grow/StatisticsActivity.kt index 4d65b25b..7b15550c 100644 --- a/app/src/main/java/me/anon/grow/StatisticsActivity.kt +++ b/app/src/main/java/me/anon/grow/StatisticsActivity.kt @@ -3,6 +3,8 @@ package me.anon.grow import android.os.Bundle import kotlinx.android.synthetic.main.fragment_holder.* import me.anon.grow.fragment.StatisticsFragment +import me.anon.grow.fragment.StatisticsFragment2 +import me.anon.lib.manager.PlantManager class StatisticsActivity : BaseActivity() { @@ -13,16 +15,18 @@ class StatisticsActivity : BaseActivity() setContentView(R.layout.fragment_holder) setSupportActionBar(toolbar) - if (intent.extras == null || !intent.hasExtra("plant")) - { - finish() - return - } +// if (intent.extras == null || !intent.hasExtra("plant")) +// { +// finish() +// return +// } + + intent.putExtra("plant", PlantManager.instance.plants[0]) if (supportFragmentManager.findFragmentByTag("fragment") == null) { supportFragmentManager.beginTransaction() - .replace(R.id.coordinator, StatisticsFragment.newInstance(intent.extras), "fragment") + .replace(R.id.fragment_holder, StatisticsFragment2.newInstance(intent.extras!!), "fragment") .commit() } } diff --git a/app/src/main/java/me/anon/grow/ViewPhotosActivity.kt b/app/src/main/java/me/anon/grow/ViewPhotosActivity.kt index 0f66ae56..351430eb 100644 --- a/app/src/main/java/me/anon/grow/ViewPhotosActivity.kt +++ b/app/src/main/java/me/anon/grow/ViewPhotosActivity.kt @@ -22,7 +22,7 @@ class ViewPhotosActivity : BaseActivity() if (supportFragmentManager.findFragmentByTag("fragment") == null) { supportFragmentManager.beginTransaction() - .replace(R.id.coordinator, ViewPhotosFragment.newInstance(intent.extras), "fragment") + .replace(R.id.fragment_holder, ViewPhotosFragment.newInstance(intent.extras), "fragment") .commit() } } diff --git a/app/src/main/java/me/anon/grow/fragment/ActionDialogFragment.java b/app/src/main/java/me/anon/grow/fragment/ActionDialogFragment.java index 5775c55d..2cf438c6 100644 --- a/app/src/main/java/me/anon/grow/fragment/ActionDialogFragment.java +++ b/app/src/main/java/me/anon/grow/fragment/ActionDialogFragment.java @@ -105,7 +105,7 @@ public ActionDialogFragment(){} { @Override public void onClick(View v) { - final DateDialogFragment fragment = new DateDialogFragment(action.getDate()); + final DateDialogFragment fragment = DateDialogFragment.newInstance(action.getDate()); fragment.setOnDateSelected(new DateDialogFragment.OnDateSelectedListener() { @Override public void onDateSelected(Calendar date) diff --git a/app/src/main/java/me/anon/grow/fragment/ActionSelectDialogFragment.java b/app/src/main/java/me/anon/grow/fragment/ActionSelectDialogFragment.java index ef9793a8..156576ab 100644 --- a/app/src/main/java/me/anon/grow/fragment/ActionSelectDialogFragment.java +++ b/app/src/main/java/me/anon/grow/fragment/ActionSelectDialogFragment.java @@ -10,6 +10,7 @@ import java.util.ArrayList; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import androidx.recyclerview.widget.DividerItemDecoration; @@ -38,26 +39,53 @@ public interface OnActionSelectedListener } @Views.InjectView(R.id.recycler_view) private RecyclerView recyclerView; + private ArrayList actions; private ActionAdapter adapter; private OnActionSelectedListener onActionSelected; + private ArrayList exclude = new ArrayList<>(); + { + exclude.add(ImageActionHolder.class); + } public void setOnActionSelectedListener(OnActionSelectedListener onActionSelected) { this.onActionSelected = onActionSelected; } - @SuppressLint("ValidFragment") - public ActionSelectDialogFragment(ArrayList actions) + public static ActionSelectDialogFragment newInstance(ArrayList actions) { - ArrayList exclude = new ArrayList<>(); - exclude.add(ImageActionHolder.class); + ActionSelectDialogFragment fragment = new ActionSelectDialogFragment(); + fragment.actions = actions; + + return fragment; + } + + public ActionSelectDialogFragment() + { + } + + @Override public void onSaveInstanceState(@NonNull Bundle outState) + { + outState.putParcelableArrayList("actions", actions); + super.onSaveInstanceState(outState); + } + + @Override public Dialog onCreateDialog(Bundle savedInstanceState) + { + View view = getActivity().getLayoutInflater().inflate(R.layout.action_list_dialog_view, null, false); + Views.inject(this, view); + + if (savedInstanceState != null) + { + actions = savedInstanceState.getParcelableArrayList("actions"); + } adapter = new ActionAdapter() { @Override public void onBindViewHolder(RecyclerView.ViewHolder vh, int index) { super.onBindViewHolder(vh, index); - int padding = (int)getResources().getDimension(R.dimen.padding_8dp); + int padding = (int)vh.itemView.getContext().getResources().getDimension(R.dimen.padding_8dp); vh.itemView.setPadding(0, 0, 0, 0); vh.itemView.findViewById(R.id.date_container).setVisibility(View.GONE); ((View)vh.itemView.findViewById(R.id.content_container).getParent()).setPadding(0, 0, 0, 0); @@ -73,17 +101,6 @@ public ActionSelectDialogFragment(ArrayList actions) adapter.setShowDate(false); adapter.setShowActions(false); adapter.setActions(null, actions, exclude); - } - - @SuppressLint("ValidFragment") - public ActionSelectDialogFragment() - { - } - - @Override public Dialog onCreateDialog(Bundle savedInstanceState) - { - View view = getActivity().getLayoutInflater().inflate(R.layout.action_list_dialog_view, null, false); - Views.inject(this, view); recyclerView.setAdapter(adapter); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); diff --git a/app/src/main/java/me/anon/grow/fragment/ActionsListFragment.java b/app/src/main/java/me/anon/grow/fragment/ActionsListFragment.java index 2501a543..0cf0bfd5 100644 --- a/app/src/main/java/me/anon/grow/fragment/ActionsListFragment.java +++ b/app/src/main/java/me/anon/grow/fragment/ActionsListFragment.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.Random; import androidx.annotation.NonNull; @@ -62,7 +63,6 @@ public class ActionsListFragment extends Fragment implements ActionAdapter.OnAct @Views.InjectView(R.id.fab_add) private View fabAdd; @Views.InjectView(R.id.recycler_view) private RecyclerView recycler; - @Views.InjectView(R.id.calendar) private MaterialCalendarView calendar; @Views.InjectView(R.id.empty) private View empty; private Plant plant; @@ -126,31 +126,10 @@ else if (getArguments() != null) adapter.setOnActionSelectListener(this); if (plant.getActions() != null && plant.getActions().size() > 0) { - calendar.addDecorator(new DayViewDecorator() - { - @Override public boolean shouldDecorate(CalendarDay calendarDay) - { - // find an action that is on this day - for (Action action : plant.getActions()) - { - LocalDate actionDate = CalendarDay.from(LocalDate.from(Instant.ofEpochMilli(action.getDate()).atZone(ZoneId.systemDefault()))).getDate(); - if (calendarDay.getDate().equals(actionDate)) - { - return true; - } - } - - return false; - } - - @Override public void decorate(DayViewFacade dayViewFacade) - { - dayViewFacade.addSpan(new DotSpan(6.0f, IntUtilsKt.resolveColor(R.attr.colorAccent, getActivity()))); - } - }); +// calendar. } - calendar.setOnDateChangedListener(new OnDateSelectedListener() + adapter.setOnDateChangedListener(new OnDateSelectedListener() { @Override public void onDateSelected(@NonNull MaterialCalendarView materialCalendarView, @NonNull CalendarDay calendarDay, boolean b) { @@ -159,8 +138,8 @@ else if (getArguments() != null) adapter.notifyDataSetChanged(); } }); - calendar.setCurrentDate(CalendarDay.today()); - calendar.setVisibility(filtered && getResources().getBoolean(R.bool.is_portrait) ? View.VISIBLE : View.GONE); + + adapter.showCalendar = filtered; adapter.setFilterDate(selectedFilterDate); setActions(); @@ -392,9 +371,9 @@ else if (action instanceof NoteAction) NoteDialogFragment dialogFragment = new NoteDialogFragment((NoteAction)action); dialogFragment.setOnDialogConfirmed(new NoteDialogFragment.OnDialogConfirmed() { - @Override public void onDialogConfirmed(String notes) + @Override public void onDialogConfirmed(String notes, Date date) { - final NoteAction noteAction = new NoteAction(System.currentTimeMillis(), notes); + final NoteAction noteAction = new NoteAction(date.getTime(), notes); plant.getActions().set(originalIndex, noteAction); PlantManager.getInstance().upsert(plant); @@ -437,7 +416,7 @@ else if (action instanceof EmptyAction) setActions(); adapter.notifyDataSetChanged(); - SnackBar.show(getActivity(), action.getAction().getPrintString() + " " + getString(R.string.updated), getString(R.string.undo), new SnackBarListener() + SnackBar.show(getActivity(), getString(action.getAction().getPrintString()) + " " + getString(R.string.updated), getString(R.string.undo), new SnackBarListener() { @Override public void onSnackBarStarted(Object o){} @Override public void onSnackBarFinished(Object o){} @@ -552,16 +531,16 @@ private void setResult() if (item.getItemId() == R.id.menu_calendar) { - calendar.setVisibility(calendar.getVisibility() == View.GONE ? View.VISIBLE : View.GONE); - filtered = calendar.getVisibility() == View.VISIBLE; + adapter.showCalendar = !adapter.showCalendar; + filtered = adapter.showCalendar; selectedFilterDate = null; fabAdd.setVisibility(View.VISIBLE); if (filtered) { fabAdd.setVisibility(View.GONE); - selectedFilterDate = CalendarDay.today(); - calendar.setSelectedDate(selectedFilterDate); + Action lastAction = plant.getActions().get(plant.getActions().size() - 1); + selectedFilterDate = CalendarDay.from(LocalDate.from(Instant.ofEpochMilli(lastAction.getDate()).atZone(ZoneId.systemDefault()))); } adapter.setFilterDate(selectedFilterDate); diff --git a/app/src/main/java/me/anon/grow/fragment/DateDialogFragment.java b/app/src/main/java/me/anon/grow/fragment/DateDialogFragment.java index f14d7761..9fa61794 100644 --- a/app/src/main/java/me/anon/grow/fragment/DateDialogFragment.java +++ b/app/src/main/java/me/anon/grow/fragment/DateDialogFragment.java @@ -10,6 +10,7 @@ import java.util.Calendar; +import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; public class DateDialogFragment extends Fragment @@ -29,19 +30,30 @@ public void setOnDateSelected(OnDateSelectedListener onDateSelected) this.onDateSelected = onDateSelected; } - @SuppressLint("ValidFragment") public DateDialogFragment(){} - @SuppressLint("ValidFragment") - public DateDialogFragment(long time) + public static DateDialogFragment newInstance(long time) { - this.time = time; + DateDialogFragment fragment = new DateDialogFragment(); + fragment.time = time; + return fragment; + } + + @Override public void onSaveInstanceState(@NonNull Bundle outState) + { + outState.putLong("time", time); + super.onSaveInstanceState(outState); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + if (savedInstanceState != null) + { + time = savedInstanceState.getLong("time"); + } + final Calendar date = Calendar.getInstance(); date.setTimeInMillis(time); diff --git a/app/src/main/java/me/anon/grow/fragment/FeedingScheduleDetailsFragment.kt b/app/src/main/java/me/anon/grow/fragment/FeedingScheduleDetailsFragment.kt index ce366fc1..4b7663ca 100644 --- a/app/src/main/java/me/anon/grow/fragment/FeedingScheduleDetailsFragment.kt +++ b/app/src/main/java/me/anon/grow/fragment/FeedingScheduleDetailsFragment.kt @@ -18,7 +18,6 @@ import me.anon.grow.ScheduleDateDetailsActivity import me.anon.lib.SnackBar import me.anon.lib.Unit import me.anon.lib.ext.T -import me.anon.lib.helper.FabAnimator import me.anon.lib.manager.ScheduleManager import me.anon.model.FeedingSchedule import me.anon.model.FeedingScheduleDate @@ -64,7 +63,7 @@ class FeedingScheduleDetailsFragment : Fragment() fab_complete.setOnClickListener { title.error = null - if (scheduleDates.isNotEmpty() && title.text.isEmpty()) + if (title.text.isEmpty()) { title.error = getString(R.string.field_required) } @@ -154,11 +153,7 @@ class FeedingScheduleDetailsFragment : Fragment() scheduleDates.remove(date) populateScheduleDates() - SnackBar().show(activity as AppCompatActivity, R.string.schedule_deleted, R.string.undo, { - FabAnimator.animateUp(fab_complete) - }, { - FabAnimator.animateDown(fab_complete) - }, { + SnackBar().show(activity as AppCompatActivity, R.string.schedule_deleted, R.string.undo, action = { scheduleDates.add(index, date) populateScheduleDates() }) @@ -170,15 +165,10 @@ class FeedingScheduleDetailsFragment : Fragment() feedingView.copy.setOnClickListener { view -> val newSchedule = Kryo().copy(date) newSchedule.id = UUID.randomUUID().toString() - val index = schedule_dates_container.indexOfChild(feedingView) - scheduleDates.add((index < 0) T scheduleDates.size - 1 ?: index, newSchedule) + scheduleDates.add(newSchedule) populateScheduleDates() - SnackBar().show(activity!!, R.string.schedule_copied, R.string.undo, { - FabAnimator.animateUp(fab_complete) - }, { - FabAnimator.animateDown(fab_complete) - }, { + SnackBar().show(activity!!, R.string.schedule_copied, R.string.undo, action = { scheduleDates.remove(newSchedule) populateScheduleDates() }) diff --git a/app/src/main/java/me/anon/grow/fragment/FeedingScheduleListFragment.kt b/app/src/main/java/me/anon/grow/fragment/FeedingScheduleListFragment.kt index da4a451e..767f2f95 100644 --- a/app/src/main/java/me/anon/grow/fragment/FeedingScheduleListFragment.kt +++ b/app/src/main/java/me/anon/grow/fragment/FeedingScheduleListFragment.kt @@ -16,7 +16,6 @@ import me.anon.controller.adapter.FeedingScheduleAdapter import me.anon.grow.FeedingScheduleDetailsActivity import me.anon.grow.R import me.anon.lib.SnackBar -import me.anon.lib.helper.FabAnimator import me.anon.lib.manager.ScheduleManager import java.util.* @@ -47,11 +46,7 @@ class FeedingScheduleListFragment : Fragment() adapter.notifyDataSetChanged() checkAdapter() - SnackBar().show(activity as AppCompatActivity, R.string.schedule_deleted, R.string.undo, { - FabAnimator.animateUp(fab_add) - }, { - FabAnimator.animateDown(fab_add) - }, { + SnackBar().show(activity as AppCompatActivity, R.string.schedule_deleted, R.string.undo, action = { ScheduleManager.instance.schedules.add(index, schedule) ScheduleManager.instance.save() adapter.items = ScheduleManager.instance.schedules @@ -69,11 +64,7 @@ class FeedingScheduleListFragment : Fragment() adapter.notifyDataSetChanged() checkAdapter() - SnackBar().show(activity as AppCompatActivity, R.string.schedule_copied, R.string.undo, { - FabAnimator.animateUp(fab_add) - }, { - FabAnimator.animateDown(fab_add) - }, { + SnackBar().show(activity as AppCompatActivity, R.string.schedule_copied, R.string.undo, action = { ScheduleManager.instance.schedules.remove(newSchedule) ScheduleManager.instance.save() adapter.items = ScheduleManager.instance.schedules diff --git a/app/src/main/java/me/anon/grow/fragment/FeedingScheduleSelectDialogFragment.java b/app/src/main/java/me/anon/grow/fragment/FeedingScheduleSelectDialogFragment.java index 3b0119c3..2a504c56 100644 --- a/app/src/main/java/me/anon/grow/fragment/FeedingScheduleSelectDialogFragment.java +++ b/app/src/main/java/me/anon/grow/fragment/FeedingScheduleSelectDialogFragment.java @@ -1,6 +1,5 @@ package me.anon.grow.fragment; -import android.annotation.SuppressLint; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; @@ -9,6 +8,9 @@ import android.view.WindowManager; import android.widget.LinearLayout; +import java.util.ArrayList; + +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import androidx.recyclerview.widget.DividerItemDecoration; @@ -43,7 +45,7 @@ public interface OnFeedingSelectedListener @Views.InjectView(R.id.recycler_view) private RecyclerView recyclerView; private FeedingDateAdapter adapter; private OnFeedingSelectedListener onFeedingSelected; - private Plant plant; + private ArrayList plants; private FeedingSchedule schedule; public void setOnFeedingSelectedListener(OnFeedingSelectedListener onFeedingSelected) @@ -51,26 +53,38 @@ public void setOnFeedingSelectedListener(OnFeedingSelectedListener onFeedingSele this.onFeedingSelected = onFeedingSelected; } - @SuppressLint("ValidFragment") - public FeedingScheduleSelectDialogFragment(FeedingSchedule schedule, Plant plant) + public static FeedingScheduleSelectDialogFragment newInstance(FeedingSchedule schedule, ArrayList plants) { - this.plant = plant; - this.schedule = schedule; + FeedingScheduleSelectDialogFragment fragment = new FeedingScheduleSelectDialogFragment(); + fragment.plants = new ArrayList(plants); + fragment.schedule = schedule; + return fragment; } - - @SuppressLint("ValidFragment") public FeedingScheduleSelectDialogFragment() { } + @Override public void onSaveInstanceState(@NonNull Bundle outState) + { + outState.putParcelableArrayList("plants", plants); + outState.putParcelable("schedule", schedule); + super.onSaveInstanceState(outState); + } + @Override public Dialog onCreateDialog(Bundle savedInstanceState) { View view = getActivity().getLayoutInflater().inflate(R.layout.feeding_list_dialog_view, null, false); Views.inject(this, view); + if (savedInstanceState != null) + { + plants = savedInstanceState.getParcelableArrayList("plants"); + schedule = savedInstanceState.getParcelable("schedule"); + } + adapter = new FeedingDateAdapter(); - adapter.setPlant(plant); + adapter.setPlants(plants); adapter.setItems(schedule.getSchedules()); recyclerView.setAdapter(adapter); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); @@ -101,25 +115,18 @@ public FeedingScheduleSelectDialogFragment() { @Override public void onShow(DialogInterface dialog) { - PlantStage lastStage = adapter.getLastStage(); - int days = (int)TimeHelper.toDays(adapter.getPlantStages().get(lastStage)); - int suggestedIndex = 0; - for (FeedingScheduleDate feedingScheduleDate : adapter.getItems()) + for (int index = 0; index < recyclerView.getAdapter().getItemCount(); index++) { - if (lastStage.ordinal() >= feedingScheduleDate.getStageRange()[0].ordinal()) + recyclerView.scrollToPosition(index); + for (int childIndex = 0; childIndex < recyclerView.getChildCount(); childIndex++) { - if (days >= feedingScheduleDate.getDateRange()[0] - && ((days <= feedingScheduleDate.getDateRange()[1] && lastStage.ordinal() == feedingScheduleDate.getStageRange()[0].ordinal()) - || (lastStage.ordinal() < feedingScheduleDate.getStageRange()[1].ordinal()))) + if (recyclerView.getChildAt(childIndex).getTag() == Boolean.TRUE) { - break; + recyclerView.scrollToPosition(recyclerView.getChildAdapterPosition(recyclerView.getChildAt(childIndex))); + return; } } - - suggestedIndex++; } - - recyclerView.scrollToPosition(suggestedIndex); } }); diff --git a/app/src/main/java/me/anon/grow/fragment/GardenFragment.java b/app/src/main/java/me/anon/grow/fragment/GardenFragment.java index cb961293..c64698f3 100644 --- a/app/src/main/java/me/anon/grow/fragment/GardenFragment.java +++ b/app/src/main/java/me/anon/grow/fragment/GardenFragment.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.LinkedHashSet; import java.util.Set; @@ -49,9 +50,7 @@ import me.anon.lib.event.GardenChangeEvent; import me.anon.lib.export.ExportHelper; import me.anon.lib.export.ExportProcessor; -import me.anon.lib.ext.IntUtilsKt; import me.anon.lib.helper.BusHelper; -import me.anon.lib.helper.FabAnimator; import me.anon.lib.manager.GardenManager; import me.anon.lib.manager.PlantManager; import me.anon.model.EmptyAction; @@ -67,7 +66,7 @@ public class GardenFragment extends Fragment private PlantAdapter adapter; private Garden garden; - public static GardenFragment newInstance(@Nullable Garden garden) + public static GardenFragment newInstance(Garden garden) { GardenFragment fragment = new GardenFragment(); fragment.garden = garden; @@ -180,7 +179,6 @@ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, Recycle { for (int index = fromPosition; index < toPosition; index++) { - Collections.swap(PlantManager.getInstance().getPlants(), index, index + 1); Collections.swap(adapter.getPlants(), index, index + 1); adapter.notifyItemChanged(index, Boolean.TRUE); adapter.notifyItemChanged(index + 1, Boolean.TRUE); @@ -190,7 +188,6 @@ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, Recycle { for (int index = fromPosition; index > toPosition; index--) { - Collections.swap(PlantManager.getInstance().getPlants(), index, index - 1); Collections.swap(adapter.getPlants(), index, index - 1); adapter.notifyItemChanged(index, Boolean.TRUE); adapter.notifyItemChanged(index - 1, Boolean.TRUE); @@ -207,7 +204,7 @@ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, Recycle if (filterList == null) { filterList = new ArrayList<>(); - Set prefsList = androidx.preference.PreferenceManager.getDefaultSharedPreferences(getActivity()).getStringSet("filter_list", null); + Set prefsList = androidx.preference.PreferenceManager.getDefaultSharedPreferences(getActivity()).getStringSet("new_filter_list", null); if (prefsList == null) { filterList.addAll(Arrays.asList(PlantStage.values())); @@ -218,8 +215,7 @@ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, Recycle { try { - int ordinal = IntUtilsKt.toSafeInt(s); - filterList.add(PlantStage.values()[ordinal]); + filterList.add(PlantStage.valueOf(s)); } catch (Exception e) { @@ -269,7 +265,11 @@ private synchronized void saveCurrentState() } garden.setPlantIds(orderedPlantIds); - GardenManager.getInstance().save(); + + if (GardenManager.getInstance().getGardens().indexOf(garden) > -1) + { + GardenManager.getInstance().upsert(garden); + } } PlantManager.getInstance().upsert(plants); @@ -295,6 +295,7 @@ private synchronized void saveCurrentState() Intent feed = new Intent(getActivity(), AddWateringActivity.class); feed.putExtra("plant_index", plants); + feed.putExtra("garden_index", GardenManager.getInstance().getGardens().indexOf(garden)); startActivityForResult(feed, 2); } @@ -312,28 +313,7 @@ private synchronized void saveCurrentState() saveCurrentState(); - SnackBar.show(getActivity(), getString(R.string.snackbar_action_add), new SnackBarListener() - { - @Override public void onSnackBarStarted(Object o) - { - if (getView() != null) - { - FabAnimator.animateUp(getView().findViewById(R.id.fab_add)); - } - } - - @Override public void onSnackBarFinished(Object o) - { - if (getView() != null) - { - FabAnimator.animateDown(getView().findViewById(R.id.fab_add)); - } - } - - @Override public void onSnackBarAction(View v) - { - } - }); + SnackBar.show(getActivity(), getString(R.string.snackbar_action_add), null); } }); dialogFragment.show(getFragmentManager(), null); @@ -344,38 +324,17 @@ private synchronized void saveCurrentState() NoteDialogFragment dialogFragment = new NoteDialogFragment(); dialogFragment.setOnDialogConfirmed(new NoteDialogFragment.OnDialogConfirmed() { - @Override public void onDialogConfirmed(String notes) + @Override public void onDialogConfirmed(String notes, Date date) { for (Plant plant : adapter.getPlants()) { - NoteAction action = new NoteAction(System.currentTimeMillis(), notes); + NoteAction action = new NoteAction(date.getTime(), notes); plant.getActions().add(action); } saveCurrentState(); - SnackBar.show(getActivity(), getString(R.string.snackbar_note_add), new SnackBarListener() - { - @Override public void onSnackBarStarted(Object o) - { - if (getView() != null) - { - FabAnimator.animateUp(getView().findViewById(R.id.fab_add)); - } - } - - @Override public void onSnackBarFinished(Object o) - { - if (getView() != null) - { - FabAnimator.animateDown(getView().findViewById(R.id.fab_add)); - } - } - - @Override public void onSnackBarAction(View v) - { - } - }); + SnackBar.show(getActivity(), getString(R.string.snackbar_note_add), null); } }); dialogFragment.show(getFragmentManager(), null); @@ -404,29 +363,7 @@ private synchronized void saveCurrentState() { adapter.notifyDataSetChanged(); saveCurrentState(); - SnackBar.show(getActivity(), getString(R.string.snackbar_watering_add), new SnackBarListener() - { - @Override public void onSnackBarStarted(Object o) - { - if (getView() != null) - { - FabAnimator.animateUp(getView().findViewById(R.id.fab_add)); - } - } - - @Override public void onSnackBarAction(View v) - { - - } - - @Override public void onSnackBarFinished(Object o) - { - if (getView() != null) - { - FabAnimator.animateDown(getView().findViewById(R.id.fab_add)); - } - } - }); + SnackBar.show(getActivity(), getString(R.string.snackbar_watering_add), null); } } @@ -440,8 +377,20 @@ private synchronized void saveCurrentState() menu.findItem(R.id.export_garden).setVisible(true); menu.findItem(R.id.delete_garden).setVisible(true); - int[] ids = {R.id.filter_germination, R.id.filter_vegetation, R.id.filter_seedling, R.id.filter_cutting, R.id.filter_flowering, R.id.filter_drying, R.id.filter_curing, R.id.filter_harvested, R.id.filter_planted}; - PlantStage[] stages = {PlantStage.GERMINATION, PlantStage.VEGETATION, PlantStage.SEEDLING, PlantStage.CUTTING, PlantStage.FLOWER, PlantStage.DRYING, PlantStage.CURING, PlantStage.HARVESTED, PlantStage.PLANTED}; + int[] ids = { + R.id.filter_planted, + R.id.filter_germination, + R.id.filter_seedling, + R.id.filter_cutting, + R.id.filter_vegetation, + R.id.filter_budding, + R.id.filter_flowering, + R.id.filter_ripening, + R.id.filter_drying, + R.id.filter_curing, + R.id.filter_harvested + }; + PlantStage[] stages = PlantStage.values(); for (int index = 0; index < ids.length; index++) { @@ -508,9 +457,10 @@ else if (item.getItemId() == R.id.delete_garden) final int defaultGarden = PreferenceManager.getDefaultSharedPreferences(getActivity()).getInt("default_garden", -1); PreferenceManager.getDefaultSharedPreferences(getActivity()).edit().remove("default_garden").apply(); - GardenManager.getInstance().getGardens().remove(garden); + GardenManager.getInstance().getGardens().remove(oldIndex); GardenManager.getInstance().save(); + SnackBar.show(getActivity(), R.string.snackbar_garden_deleted, R.string.undo, new SnackBarListener() { @Override public void onSnackBarStarted(Object o){} @@ -548,7 +498,19 @@ else if (item.getItemId() == R.id.delete_garden) saveCurrentState(); } - int[] ids = {R.id.filter_planted, R.id.filter_germination, R.id.filter_seedling, R.id.filter_cutting, R.id.filter_vegetation, R.id.filter_flowering, R.id.filter_drying, R.id.filter_curing, R.id.filter_harvested}; + int[] ids = { + R.id.filter_planted, + R.id.filter_germination, + R.id.filter_seedling, + R.id.filter_cutting, + R.id.filter_vegetation, + R.id.filter_budding, + R.id.filter_flowering, + R.id.filter_ripening, + R.id.filter_drying, + R.id.filter_curing, + R.id.filter_harvested + }; PlantStage[] stages = PlantStage.values(); for (int index = 0; index < ids.length; index++) @@ -572,10 +534,10 @@ else if (item.getItemId() == R.id.delete_garden) Set stageOrdinals = new LinkedHashSet<>(); for (PlantStage plantStage : filterList) { - stageOrdinals.add(plantStage.ordinal() + ""); + stageOrdinals.add(plantStage.name()); } androidx.preference.PreferenceManager.getDefaultSharedPreferences(getActivity()).edit() - .putStringSet("filter_list", stageOrdinals) + .putStringSet("new_filter_list", stageOrdinals) .apply(); if (filter) diff --git a/app/src/main/java/me/anon/grow/fragment/GardenHostFragment.kt b/app/src/main/java/me/anon/grow/fragment/GardenHostFragment.kt index 592f4746..e18d81df 100644 --- a/app/src/main/java/me/anon/grow/fragment/GardenHostFragment.kt +++ b/app/src/main/java/me/anon/grow/fragment/GardenHostFragment.kt @@ -28,13 +28,13 @@ class GardenHostFragment : Fragment() } childFragmentManager.findFragmentByTag("child_fragment") ?: let { - childFragmentManager.beginTransaction().replace(R.id.child_fragment_holder, GardenFragment.newInstance(garden), "child_fragment").commit() + childFragmentManager.beginTransaction().replace(R.id.fragment_holder, GardenFragment.newInstance(garden), "child_fragment").commit() } tabs.setOnNavigationItemSelectedListener { childFragmentManager.beginTransaction() .setCustomAnimations(R.anim.fade_in, R.anim.fade_out) - .replace(R.id.child_fragment_holder, when (it.itemId) + .replace(R.id.fragment_holder, when (it.itemId) { R.id.view_plants -> GardenFragment.newInstance(garden) R.id.view_history -> GardenTrackerFragment.newInstance(garden) @@ -49,7 +49,7 @@ class GardenHostFragment : Fragment() override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) - childFragmentManager.findFragmentById(R.id.child_fragment_holder)?.onActivityResult(requestCode, resultCode, data) + childFragmentManager.findFragmentById(R.id.fragment_holder)?.onActivityResult(requestCode, resultCode, data) } override fun onSaveInstanceState(outState: Bundle) diff --git a/app/src/main/java/me/anon/grow/fragment/GardenTrackerFragment.kt b/app/src/main/java/me/anon/grow/fragment/GardenTrackerFragment.kt index 7ed6ccd0..8d7a6dfc 100644 --- a/app/src/main/java/me/anon/grow/fragment/GardenTrackerFragment.kt +++ b/app/src/main/java/me/anon/grow/fragment/GardenTrackerFragment.kt @@ -17,6 +17,7 @@ import com.github.mikephil.charting.components.MarkerView import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.highlight.Highlight import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.MPPointF import kotlinx.android.synthetic.main.data_label_stub.view.* import kotlinx.android.synthetic.main.garden_tracker_view.* import me.anon.controller.adapter.GardenActionAdapter @@ -99,8 +100,8 @@ class GardenTrackerFragment : Fragment() (activity as MainActivity).toolbarLayout.findViewById(R.id.note).setOnClickListener { val dialogFragment = NoteDialogFragment() - dialogFragment.setOnDialogConfirmed { - garden.actions.add(NoteAction(notes = it)) + dialogFragment.setOnDialogConfirmed { notes, date -> + garden.actions.add(NoteAction(notes = notes, date = date.time)) updateDataReferences() } dialogFragment.show(childFragmentManager, null) @@ -257,8 +258,13 @@ class GardenTrackerFragment : Fragment() is NoteAction -> { val dialogFragment = NoteDialogFragment(action) - dialogFragment.setOnDialogConfirmed { notes -> - if (index > -1) garden.actions[index].notes = notes + dialogFragment.setOnDialogConfirmed { notes, dates -> + if (index > -1) + { + garden.actions[index].notes = notes + garden.actions[index].date = dates.time + } + updateDataReferences() } dialogFragment.show(childFragmentManager, null) @@ -303,7 +309,8 @@ class GardenTrackerFragment : Fragment() if (data_container.findViewById(R.id.stats_temp) == null) data_container.addView(view) } StatsHelper.setTempData(garden, activity!!, temp, tempAdditional) - temp.markerView = object : MarkerView(context, R.layout.chart_marker) + + temp.marker = object : MarkerView(context, R.layout.chart_marker) { override fun refreshContent(e: Entry, highlight: Highlight) { @@ -313,18 +320,11 @@ class GardenTrackerFragment : Fragment() if (action != null) date = "\n" + timeFormat.format(Date(action.date)) - (findViewById(R.id.content) as TextView).text = e.getVal().formatWhole() + "°" + tempUnit.label + date + (findViewById(R.id.content) as TextView).text = e.y.formatWhole() + "°" + tempUnit.label + date + super.refreshContent(e, highlight) } - override fun getXOffset(xpos: Float): Int - { - return -(width / 2) - } - - override fun getYOffset(ypos: Float): Int - { - return -height - } + override fun getOffset(): MPPointF = MPPointF.getInstance(-(width / 2f), -(height * 1.2f)) } temp.notifyDataSetChanged() temp.postInvalidate() @@ -342,7 +342,7 @@ class GardenTrackerFragment : Fragment() if (data_container.findViewById(R.id.stats_humidity) == null) data_container.addView(view) } StatsHelper.setHumidityData(garden, activity!!, humidity, humidityAdditional) - humidity.markerView = object : MarkerView(context, R.layout.chart_marker) + humidity.marker = object : MarkerView(context, R.layout.chart_marker) { override fun refreshContent(e: Entry, highlight: Highlight) { @@ -351,18 +351,11 @@ class GardenTrackerFragment : Fragment() var date = "" if (action != null) date = "\n" + timeFormat.format(Date(action.date)) - (findViewById(R.id.content) as TextView).text = e.getVal().toInt().toString() + "%" + date - } - - override fun getXOffset(xpos: Float): Int - { - return -(width / 2) + (findViewById(R.id.content) as TextView).text = e.y.toInt().toString() + "%" + date + super.refreshContent(e, highlight) } - override fun getYOffset(ypos: Float): Int - { - return -height - } + override fun getOffset(): MPPointF = MPPointF.getInstance(-(width / 2f), -(height * 1.2f)) } humidity.notifyDataSetChanged() humidity.postInvalidate() @@ -378,7 +371,7 @@ class GardenTrackerFragment : Fragment() delete_humidity.visibility = View.GONE } - override fun onValueSelected(e: Entry?, dataSetIndex: Int, h: Highlight?) + override fun onValueSelected(e: Entry?, h: Highlight?) { edit_humidity.visibility = View.VISIBLE delete_humidity.visibility = View.VISIBLE @@ -405,7 +398,7 @@ class GardenTrackerFragment : Fragment() delete_temp.visibility = View.GONE } - override fun onValueSelected(e: Entry?, dataSetIndex: Int, h: Highlight?) + override fun onValueSelected(e: Entry?, h: Highlight?) { edit_temp.visibility = View.VISIBLE delete_temp.visibility = View.VISIBLE diff --git a/app/src/main/java/me/anon/grow/fragment/HumidityDialogFragment.kt b/app/src/main/java/me/anon/grow/fragment/HumidityDialogFragment.kt index 98539324..eb8bf6c8 100644 --- a/app/src/main/java/me/anon/grow/fragment/HumidityDialogFragment.kt +++ b/app/src/main/java/me/anon/grow/fragment/HumidityDialogFragment.kt @@ -59,7 +59,7 @@ class HumidityDialogFragment(var action: HumidityChange? = null, val callback: ( view.findViewById(R.id.date).text = dateStr view.findViewById(R.id.date).setOnClickListener { - val fragment = DateDialogFragment(action!!.date) + val fragment = DateDialogFragment.newInstance(action!!.date) fragment.setOnDateSelected(object : DateDialogFragment.OnDateSelectedListener { override fun onDateSelected(date: Calendar) diff --git a/app/src/main/java/me/anon/grow/fragment/ImageLightboxDialog.java b/app/src/main/java/me/anon/grow/fragment/ImageLightboxDialog.java index 19e20cfb..fa30c8ec 100644 --- a/app/src/main/java/me/anon/grow/fragment/ImageLightboxDialog.java +++ b/app/src/main/java/me/anon/grow/fragment/ImageLightboxDialog.java @@ -130,8 +130,15 @@ protected ImagePagerAdapter(String[] images, ViewPager pager) try { ExifInterface exifInterface = new ExifInterface(images[position]); - exifInterface.setAttribute("UserComment", ((EditText)((View)object).findViewById(R.id.comment)).getText().toString()); - exifInterface.saveAttributes(); + String comment = exifInterface.getAttribute("UserComment"); + if (comment == null) comment = ""; + String text = ((EditText)((View)object).findViewById(R.id.comment)).getText().toString(); + + if (!text.equalsIgnoreCase(comment)) + { + exifInterface.setAttribute("UserComment", ((EditText)((View)object).findViewById(R.id.comment)).getText().toString()); + exifInterface.saveAttributes(); + } } catch (IOException e) { diff --git a/app/src/main/java/me/anon/grow/fragment/NoteDialogFragment.java b/app/src/main/java/me/anon/grow/fragment/NoteDialogFragment.java index 4521a2dd..79636655 100644 --- a/app/src/main/java/me/anon/grow/fragment/NoteDialogFragment.java +++ b/app/src/main/java/me/anon/grow/fragment/NoteDialogFragment.java @@ -9,6 +9,11 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; +import android.widget.TextView; + +import java.text.DateFormat; +import java.util.Calendar; +import java.util.Date; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; @@ -22,11 +27,13 @@ public class NoteDialogFragment extends DialogFragment { public static interface OnDialogConfirmed { - public void onDialogConfirmed(String notes); + public void onDialogConfirmed(String notes, Date date); } @Views.InjectView(R.id.notes) private EditText notes; + @Views.InjectView(R.id.date) private TextView date; private NoteAction action; + private Date actionDate = new Date(); private OnDialogConfirmed onDialogConfirmed; public DialogInterface.OnCancelListener onCancelListener; @@ -55,11 +62,45 @@ public NoteDialogFragment(NoteAction action) Views.inject(this, view); + actionDate = Calendar.getInstance().getTime(); if (action != null) { notes.setText(action.getNotes()); + actionDate = new Date(action.getDate()); } + final DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(getActivity()); + final DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(getActivity()); + + String dateStr = dateFormat.format(actionDate) + " " + timeFormat.format(actionDate); + + this.date.setText(dateStr); + this.date.setOnClickListener(new View.OnClickListener() + { + @Override public void onClick(View v) + { + final DateDialogFragment fragment = DateDialogFragment.newInstance(actionDate.getTime()); + fragment.setOnDateSelected(new DateDialogFragment.OnDateSelectedListener() + { + @Override public void onDateSelected(Calendar date) + { + String dateStr = dateFormat.format(date.getTime()) + " " + timeFormat.format(date.getTime()); + NoteDialogFragment.this.date.setText(dateStr); + + actionDate = date.getTime(); + + onCancelled(); + } + + @Override public void onCancelled() + { + getChildFragmentManager().beginTransaction().remove(fragment).commit(); + } + }); + getChildFragmentManager().beginTransaction().add(fragment, "date").commit(); + } + }); + dialog.setView(view); dialog.setPositiveButton(action == null ? R.string.add : R.string.edit, new DialogInterface.OnClickListener() { @@ -67,7 +108,10 @@ public NoteDialogFragment(NoteAction action) { if (onDialogConfirmed != null) { - onDialogConfirmed.onDialogConfirmed(TextUtils.isEmpty(notes.getText()) ? null : notes.getText().toString()); + onDialogConfirmed.onDialogConfirmed( + TextUtils.isEmpty(notes.getText()) ? null : notes.getText().toString(), + actionDate + ); } } }); diff --git a/app/src/main/java/me/anon/grow/fragment/PlantDetailsFragment.java b/app/src/main/java/me/anon/grow/fragment/PlantDetailsFragment.java index 583cd952..f933dbf6 100644 --- a/app/src/main/java/me/anon/grow/fragment/PlantDetailsFragment.java +++ b/app/src/main/java/me/anon/grow/fragment/PlantDetailsFragment.java @@ -7,11 +7,13 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; +import android.database.Cursor; import android.media.MediaScannerConnection; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.provider.DocumentsContract; import android.provider.MediaStore; import android.text.Html; import android.text.TextUtils; @@ -32,16 +34,14 @@ import com.esotericsoftware.kryo.Kryo; -import org.jetbrains.annotations.NotNull; - import java.io.File; import java.io.IOException; import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; -import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.Set; import java.util.TreeSet; import java.util.UUID; @@ -71,7 +71,6 @@ import me.anon.lib.Views; import me.anon.lib.export.ExportHelper; import me.anon.lib.export.ExportProcessor; -import me.anon.lib.helper.FabAnimator; import me.anon.lib.helper.NotificationHelper; import me.anon.lib.helper.PermissionHelper; import me.anon.lib.manager.FileManager; @@ -333,7 +332,7 @@ private void setUi() { @Override public void onClick(View v) { - final DateDialogFragment fragment = new DateDialogFragment(plant.getPlantDate()); + final DateDialogFragment fragment = DateDialogFragment.newInstance(plant.getPlantDate()); fragment.setOnDateSelected(new DateDialogFragment.OnDateSelectedListener() { @Override public void onDateSelected(Calendar newDate) @@ -377,9 +376,9 @@ private void setUi() NoteDialogFragment dialogFragment = new NoteDialogFragment(); dialogFragment.setOnDialogConfirmed(new NoteDialogFragment.OnDialogConfirmed() { - @Override public void onDialogConfirmed(String notes) + @Override public void onDialogConfirmed(String notes, Date date) { - final NoteAction action = new NoteAction(System.currentTimeMillis(), notes); + final NoteAction action = new NoteAction(date.getTime(), notes); plant.getActions().add(action); PlantManager.getInstance().upsert(plant); @@ -388,17 +387,12 @@ private void setUi() { @Override public void onSnackBarStarted(Object o) { - if (getView() != null) - { - FabAnimator.animateUp(getView().findViewById(R.id.fab_complete)); - } } @Override public void onSnackBarFinished(Object o) { if (getView() != null) { - FabAnimator.animateDown(getView().findViewById(R.id.fab_complete)); PlantWidgetProvider.triggerUpdateAll(getView().getContext()); } } @@ -565,18 +559,10 @@ else if (requestCode == ACTIVITY_REQUEST_FEEDING) { @Override public void onSnackBarStarted(Object o) { - if (getView() != null) - { - FabAnimator.animateUp(getView().findViewById(R.id.fab_complete)); - } } @Override public void onSnackBarFinished(Object o) { - if (getView() != null) - { - FabAnimator.animateDown(getView().findViewById(R.id.fab_complete)); - } } @Override public void onSnackBarAction(View v) @@ -595,32 +581,7 @@ else if (requestCode == ACTIVITY_REQUEST_FEEDING) PlantManager.getInstance().getPlants().get(index).getActions().add(copy); } - SnackBar.show(getActivity(), R.string.waterings_added, new SnackBarListener() - { - @Override public void onSnackBarStarted(Object o) - { - if (getView() != null) - { - FabAnimator.animateUp(getView().findViewById(R.id.fab_complete)); - } - } - - @Override public void onSnackBarFinished(Object o) - { - if (getView() != null) - { - FabAnimator.animateDown(getView().findViewById(R.id.fab_complete)); - } - } - - @Override public void onSnackBarAction(@NotNull View o) - { - if (getView() != null) - { - FabAnimator.animateUp(getView().findViewById(R.id.fab_complete)); - } - } - }); + SnackBar.show(getActivity(), R.string.waterings_added, null); } }); dialog.show(getFragmentManager(), "plant-select"); @@ -634,10 +595,10 @@ else if (requestCode == ACTIVITY_REQUEST_PHOTO_GALLERY) // choose image from gal { if (data == null) return; - ArrayList images = new ArrayList<>(); + HashMap images = new HashMap(); if (data.getData() != null) { - images.add(data.getData()); + images.put(data.getData(), System.currentTimeMillis()); try { @@ -659,10 +620,28 @@ else if (requestCode == ACTIVITY_REQUEST_PHOTO_GALLERY) // choose image from gal { for (int index = 0; index < data.getClipData().getItemCount(); index++) { - images.add(data.getClipData().getItemAt(index).getUri()); + images.put(data.getClipData().getItemAt(index).getUri(), System.currentTimeMillis()); + } + } + + for (Uri key : images.keySet()) + { + try + { + Cursor query = getActivity().getContentResolver().query(key, null, + DocumentsContract.Document.COLUMN_DOCUMENT_ID + " = " + key.getPath(), null, null); + long modifiedDate = images.get(key); + int modifiedIndex = query.getColumnIndex(DocumentsContract.Document.COLUMN_LAST_MODIFIED); + while (query.moveToNext()) { + modifiedDate = query.getLong(modifiedIndex); + break; + } + + images.put(key, modifiedDate == -1 ? images.get(key) : modifiedDate); + query.close(); } + catch (Exception e){} } - images.removeAll(Collections.singleton(null)); NotificationHelper.sendDataTaskNotification(getActivity(), getString(R.string.app_name), getString(R.string.import_progress_warning)); new ImportTask(getActivity(), new AsyncCallback() @@ -697,19 +676,12 @@ else if (requestCode == ACTIVITY_REQUEST_LAST_WATER) SnackBar.show(getActivity(), R.string.snackbar_image_added, R.string.snackbar_action_take_another, new SnackBarListener() { - @Override public void onSnackBarStarted(Object o) - { - if (getView() != null) - { - FabAnimator.animateUp(getView().findViewById(R.id.fab_complete)); - } - } + @Override public void onSnackBarStarted(Object o){} @Override public void onSnackBarFinished(Object o) { if (getView() != null) { - FabAnimator.animateDown(getView().findViewById(R.id.fab_complete)); PlantWidgetProvider.triggerUpdateAll(getView().getContext()); } } @@ -802,6 +774,7 @@ private void finishPhotoIntent() if (getActivity() != null) { + getActivity().setResult(Activity.RESULT_CANCELED); getActivity().finish(); } } @@ -828,18 +801,10 @@ else if (item.getItemId() == R.id.duplicate) { @Override public void onSnackBarStarted(Object o) { - if (getView() != null) - { - FabAnimator.animateUp(getView().findViewById(R.id.fab_complete)); - } } @Override public void onSnackBarFinished(Object o) { - if (getView() != null) - { - FabAnimator.animateDown(getView().findViewById(R.id.fab_complete)); - } } @Override public void onSnackBarAction(View v) @@ -882,17 +847,12 @@ else if (item.getItemId() == R.id.export) { @Override public void onSnackBarStarted(Object o) { - if (getView() != null) - { - FabAnimator.animateUp(getView().findViewById(R.id.fab_complete)); - } } @Override public void onSnackBarFinished(Object o) { if (getView() != null) { - FabAnimator.animateDown(getView().findViewById(R.id.fab_complete)); PlantWidgetProvider.triggerUpdateAll(getView().getContext()); } } @@ -955,18 +915,10 @@ else if (item.getItemId() == R.id.export) { @Override public void onSnackBarStarted(Object o) { - if (getView() != null) - { - FabAnimator.animateUp(getView().findViewById(R.id.fab_add)); - } } @Override public void onSnackBarFinished(Object o) { - if (getView() != null) - { - FabAnimator.animateDown(getView().findViewById(R.id.fab_add)); - } } @Override public void onSnackBarAction(View v) @@ -1033,7 +985,7 @@ public void save() } plant.setClone(clone.isChecked()); - //PlantManager.getInstance().upsert(plant); + PlantManager.getInstance().upsert(plant); if (gardenIndex != -1) { @@ -1044,6 +996,7 @@ public void save() } } + plant = PlantManager.getInstance().getPlant(plant.getId()); Intent intent = new Intent(); intent.putExtra("plant", plant); getActivity().setIntent(intent); diff --git a/app/src/main/java/me/anon/grow/fragment/PlantListFragment.java b/app/src/main/java/me/anon/grow/fragment/PlantListFragment.java index 1d63774e..9697ea3d 100644 --- a/app/src/main/java/me/anon/grow/fragment/PlantListFragment.java +++ b/app/src/main/java/me/anon/grow/fragment/PlantListFragment.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.LinkedHashSet; import java.util.Set; @@ -39,10 +40,7 @@ import me.anon.grow.PlantDetailsActivity; import me.anon.grow.R; import me.anon.lib.SnackBar; -import me.anon.lib.SnackBarListener; import me.anon.lib.Views; -import me.anon.lib.ext.IntUtilsKt; -import me.anon.lib.helper.FabAnimator; import me.anon.lib.manager.PlantManager; import me.anon.model.EmptyAction; import me.anon.model.NoteAction; @@ -175,7 +173,7 @@ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, Recycle if (filterList == null) { filterList = new ArrayList<>(); - Set prefsList = androidx.preference.PreferenceManager.getDefaultSharedPreferences(getActivity()).getStringSet("filter_list", null); + Set prefsList = androidx.preference.PreferenceManager.getDefaultSharedPreferences(getActivity()).getStringSet("new_filter_list", null); if (prefsList == null) { filterList.addAll(Arrays.asList(PlantStage.values())); @@ -186,8 +184,7 @@ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, Recycle { try { - int ordinal = IntUtilsKt.toSafeInt(s); - filterList.add(PlantStage.values()[ordinal]); + filterList.add(PlantStage.valueOf(s)); } catch (Exception e) { @@ -273,28 +270,7 @@ private synchronized void saveCurrentState() PlantManager.getInstance().save(); - SnackBar.show(getActivity(), R.string.snackbar_action_add, new SnackBarListener() - { - @Override public void onSnackBarStarted(Object o) - { - if (getView() != null) - { - FabAnimator.animateUp(getView().findViewById(R.id.fab_add)); - } - } - - @Override public void onSnackBarFinished(Object o) - { - if (getView() != null) - { - FabAnimator.animateDown(getView().findViewById(R.id.fab_add)); - } - } - - @Override public void onSnackBarAction(View v) - { - } - }); + SnackBar.show(getActivity(), R.string.snackbar_action_add, null); } }); dialogFragment.show(getChildFragmentManager(), null); @@ -305,38 +281,17 @@ private synchronized void saveCurrentState() NoteDialogFragment dialogFragment = new NoteDialogFragment(); dialogFragment.setOnDialogConfirmed(new NoteDialogFragment.OnDialogConfirmed() { - @Override public void onDialogConfirmed(String notes) + @Override public void onDialogConfirmed(String notes, Date date) { for (Plant plant : adapter.getPlants()) { - NoteAction action = new NoteAction(System.currentTimeMillis(), notes); + NoteAction action = new NoteAction(date.getTime(), notes); plant.getActions().add(action); } PlantManager.getInstance().save(); - SnackBar.show(getActivity(), R.string.snackbar_note_add, new SnackBarListener() - { - @Override public void onSnackBarStarted(Object o) - { - if (getView() != null) - { - FabAnimator.animateUp(getView().findViewById(R.id.fab_add)); - } - } - - @Override public void onSnackBarFinished(Object o) - { - if (getView() != null) - { - FabAnimator.animateDown(getView().findViewById(R.id.fab_add)); - } - } - - @Override public void onSnackBarAction(View v) - { - } - }); + SnackBar.show(getActivity(), R.string.snackbar_note_add, null); } }); dialogFragment.show(getChildFragmentManager(), null); @@ -348,7 +303,6 @@ private synchronized void saveCurrentState() { Plant plant = data.getParcelableExtra("plant"); PlantManager.getInstance().upsert(plant); - Log.e("TEST", "result " + plant); PlantWidgetProvider.triggerUpdateAll(getActivity()); } @@ -357,29 +311,7 @@ private synchronized void saveCurrentState() if (resultCode != Activity.RESULT_CANCELED) { adapter.notifyDataSetChanged(); - SnackBar.show(getActivity(), R.string.snackbar_watering_add, new SnackBarListener() - { - @Override public void onSnackBarStarted(Object o) - { - if (getView() != null) - { - FabAnimator.animateUp(getView().findViewById(R.id.fab_add)); - } - } - - @Override public void onSnackBarAction(View v) - { - - } - - @Override public void onSnackBarFinished(Object o) - { - if (getView() != null) - { - FabAnimator.animateDown(getView().findViewById(R.id.fab_add)); - } - } - }); + SnackBar.show(getActivity(), R.string.snackbar_watering_add, null); } } @@ -390,8 +322,20 @@ private synchronized void saveCurrentState() { inflater.inflate(R.menu.plant_list_menu, menu); - int[] ids = {R.id.filter_germination, R.id.filter_vegetation, R.id.filter_seedling, R.id.filter_cutting, R.id.filter_flowering, R.id.filter_drying, R.id.filter_curing, R.id.filter_harvested, R.id.filter_planted}; - PlantStage[] stages = {PlantStage.GERMINATION, PlantStage.VEGETATION, PlantStage.SEEDLING, PlantStage.CUTTING, PlantStage.FLOWER, PlantStage.DRYING, PlantStage.CURING, PlantStage.HARVESTED, PlantStage.PLANTED}; + int[] ids = { + R.id.filter_planted, + R.id.filter_germination, + R.id.filter_seedling, + R.id.filter_cutting, + R.id.filter_vegetation, + R.id.filter_budding, + R.id.filter_flowering, + R.id.filter_ripening, + R.id.filter_drying, + R.id.filter_curing, + R.id.filter_harvested + }; + PlantStage[] stages = PlantStage.values(); for (int index = 0; index < ids.length; index++) { @@ -419,7 +363,19 @@ private synchronized void saveCurrentState() saveCurrentState(); } - int[] ids = {R.id.filter_planted, R.id.filter_germination, R.id.filter_seedling, R.id.filter_cutting, R.id.filter_vegetation, R.id.filter_flowering, R.id.filter_drying, R.id.filter_curing, R.id.filter_harvested}; + int[] ids = { + R.id.filter_planted, + R.id.filter_germination, + R.id.filter_seedling, + R.id.filter_cutting, + R.id.filter_vegetation, + R.id.filter_budding, + R.id.filter_flowering, + R.id.filter_ripening, + R.id.filter_drying, + R.id.filter_curing, + R.id.filter_harvested + }; PlantStage[] stages = PlantStage.values(); for (int index = 0; index < ids.length; index++) @@ -443,11 +399,11 @@ private synchronized void saveCurrentState() Set stageOrdinals = new LinkedHashSet<>(); for (PlantStage plantStage : filterList) { - stageOrdinals.add(plantStage.ordinal() + ""); + stageOrdinals.add(plantStage.name() + ""); } PreferenceManager.getDefaultSharedPreferences(getActivity()).edit() - .putStringSet("filter_list", stageOrdinals) + .putStringSet("new_filter_list", stageOrdinals) .apply(); if (filter) @@ -460,7 +416,6 @@ private synchronized void saveCurrentState() private void filter() { - Log.e("TEST", "filtering"); ArrayList plantList = PlantManager.getInstance().getSortedPlantList(null); if (reverse) { diff --git a/app/src/main/java/me/anon/grow/fragment/SettingsFragment.java b/app/src/main/java/me/anon/grow/fragment/SettingsFragment.java index 7726cf3e..cb960c9b 100644 --- a/app/src/main/java/me/anon/grow/fragment/SettingsFragment.java +++ b/app/src/main/java/me/anon/grow/fragment/SettingsFragment.java @@ -307,6 +307,7 @@ private void populateAddons() { PreferenceManager.getDefaultSharedPreferences(getActivity()).edit().putBoolean("force_dark", (boolean)newValue).apply(); AppCompatDelegate.setDefaultNightMode((boolean)newValue ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); + getActivity().recreate(); } else if ("backup_size".equals(preference.getKey())) { diff --git a/app/src/main/java/me/anon/grow/fragment/StageDialogFragment.java b/app/src/main/java/me/anon/grow/fragment/StageDialogFragment.java index 93e4eb65..333700d5 100644 --- a/app/src/main/java/me/anon/grow/fragment/StageDialogFragment.java +++ b/app/src/main/java/me/anon/grow/fragment/StageDialogFragment.java @@ -97,7 +97,7 @@ public StageDialogFragment(){} { @Override public void onClick(View v) { - final DateDialogFragment fragment = new DateDialogFragment(action.getDate()); + final DateDialogFragment fragment = DateDialogFragment.newInstance(action.getDate()); fragment.setOnDateSelected(new DateDialogFragment.OnDateSelectedListener() { @Override public void onDateSelected(Calendar date) diff --git a/app/src/main/java/me/anon/grow/fragment/StatisticsFragment.java b/app/src/main/java/me/anon/grow/fragment/StatisticsFragment.java index e50b6c8b..1cdcc067 100644 --- a/app/src/main/java/me/anon/grow/fragment/StatisticsFragment.java +++ b/app/src/main/java/me/anon/grow/fragment/StatisticsFragment.java @@ -12,16 +12,7 @@ import com.github.mikephil.charting.charts.BarChart; import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.components.MarkerView; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.formatter.ValueFormatter; -import com.github.mikephil.charting.formatter.YAxisValueFormatter; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.utils.ViewPortHandler; import com.google.android.flexbox.FlexboxLayout; import java.util.ArrayList; @@ -38,7 +29,6 @@ import me.anon.lib.TempUnit; import me.anon.lib.Unit; import me.anon.lib.Views; -import me.anon.lib.ext.IntUtilsKt; import me.anon.lib.ext.NumberUtilsKt; import me.anon.lib.helper.StatsHelper; import me.anon.lib.helper.TimeHelper; @@ -181,26 +171,26 @@ private void setTdsStats() String[] tdsAdditional = new String[3]; StatsHelper.setTdsData(plant, getActivity(), tds, tdsAdditional, selectedTdsUnit); - tds.setMarkerView(new MarkerView(getActivity(), R.layout.chart_marker) - { - @Override - public void refreshContent(Entry e, Highlight highlight) - { - String val = NumberUtilsKt.formatWhole(e.getVal()); - - ((TextView)findViewById(R.id.content)).setText(val); - } - - @Override public int getXOffset(float xpos) - { - return -(getWidth() / 2); - } - - @Override public int getYOffset(float ypos) - { - return -getHeight(); - } - }); +// tds.setMarkerView(new MarkerView(getActivity(), R.layout.chart_marker) +// { +// @Override +// public void refreshContent(Entry e, Highlight highlight) +// { +// String val = NumberUtilsKt.formatWhole(e.getVal()); +// +// ((TextView)findViewById(R.id.content)).setText(val); +// } +// +// @Override public int getXOffset(float xpos) +// { +// return -(getWidth() / 2); +// } +// +// @Override public int getYOffset(float ypos) +// { +// return -getHeight(); +// } +// }); tds.notifyDataSetChanged(); tds.postInvalidate(); mintds.setText(tdsAdditional[0].equals(String.valueOf(Long.MAX_VALUE)) ? "0" : tdsAdditional[0]); @@ -234,34 +224,34 @@ private void setAdditiveStats() final Unit measurement = Unit.getSelectedMeasurementUnit(getActivity()); final Unit delivery = Unit.getSelectedDeliveryUnit(getActivity()); - additives.setMarkerView(new MarkerView(getActivity(), R.layout.chart_marker) - { - @Override - public void refreshContent(Entry e, Highlight highlight) - { - String val = NumberUtilsKt.formatWhole(e.getVal()); - - ((TextView)findViewById(R.id.content)).setText(val + measurement.getLabel() + "/" + delivery.getLabel()); - - int color = IntUtilsKt.resolveColor(R.attr.colorPrimary, getActivity()); - if (e.getData() instanceof Integer) - { - color = (int)e.getData(); - } - - ((TextView)findViewById(R.id.content)).setTextColor(color); - } - - @Override public int getXOffset(float xpos) - { - return -(getWidth() / 2); - } - - @Override public int getYOffset(float ypos) - { - return -getHeight(); - } - }); +// additives.setMarkerView(new MarkerView(getActivity(), R.layout.chart_marker) +// { +// @Override +// public void refreshContent(Entry e, Highlight highlight) +// { +// String val = NumberUtilsKt.formatWhole(e.getVal()); +// +// ((TextView)findViewById(R.id.content)).setText(val + measurement.getLabel() + "/" + delivery.getLabel()); +// +// int color = IntUtilsKt.resolveColor(R.attr.colorPrimary, getActivity()); +// if (e.getData() instanceof Integer) +// { +// color = (int)e.getData(); +// } +// +// ((TextView)findViewById(R.id.content)).setTextColor(color); +// } +// +// @Override public int getXOffset(float xpos) +// { +// return -(getWidth() / 2); +// } +// +// @Override public int getYOffset(float ypos) +// { +// return -getHeight(); +// } +// }); additives.notifyDataSetChanged(); additives.postInvalidate(); @@ -407,45 +397,45 @@ private void setStatistics() labels[index--] = getString(plantStage.getPrintString()); } - entry.add(new BarEntry(yVals, 0)); - - BarDataSet set = new BarDataSet(entry, ""); - set.setColors(statsColours); - set.setStackLabels(labels); - set.setValueTextSize(12.0f); - set.setValueTextColor(IntUtilsKt.resolveColor(R.attr.chart_label, getActivity())); - set.setHighlightEnabled(false); - - BarData data = new BarData(new String[] { "" }, set); - data.setValueFormatter(new ValueFormatter() - { - @Override public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) - { - return (int)value + getString(R.string.day_abbr); - } - }); - - StatsHelper.styleGraph(stagesChart); - - stagesChart.getXAxis().setLabelsToSkip(0); - stagesChart.getAxisLeft().setValueFormatter(new YAxisValueFormatter() - { - @Override public String getFormattedValue(float value, YAxis yAxis) - { - return "" + (int)value; - } - }); - stagesChart.getAxisRight().setValueFormatter(new YAxisValueFormatter() - { - @Override public String getFormattedValue(float value, YAxis yAxis) - { - return "" + (int)value; - } - }); - - stagesChart.setMarkerView(null); - stagesChart.setHighlightPerTapEnabled(false); - stagesChart.getAxisLeft().setStartAtZero(true); - stagesChart.setData(data); +// entry.add(new BarEntry(yVals, 0)); +// +// BarDataSet set = new BarDataSet(entry, ""); +// set.setColors(statsColours); +// set.setStackLabels(labels); +// set.setValueTextSize(12.0f); +// set.setValueTextColor(IntUtilsKt.resolveColor(R.attr.chart_label, getActivity())); +// set.setHighlightEnabled(false); +// +// BarData data = new BarData(new String[] { "" }, set); +// data.setValueFormatter(new ValueFormatter() +// { +// @Override public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) +// { +// return (int)value + getString(R.string.day_abbr); +// } +// }); +// +// StatsHelper.styleGraph(stagesChart); +// +// stagesChart.getXAxis().setLabelsToSkip(0); +// stagesChart.getAxisLeft().setValueFormatter(new YAxisValueFormatter() +// { +// @Override public String getFormattedValue(float value, YAxis yAxis) +// { +// return "" + (int)value; +// } +// }); +// stagesChart.getAxisRight().setValueFormatter(new YAxisValueFormatter() +// { +// @Override public String getFormattedValue(float value, YAxis yAxis) +// { +// return "" + (int)value; +// } +// }); +// +// stagesChart.setMarkerView(null); +// stagesChart.setHighlightPerTapEnabled(false); +// stagesChart.getAxisLeft().setStartAtZero(true); +// stagesChart.setData(data); } } diff --git a/app/src/main/java/me/anon/grow/fragment/StatisticsFragment2.kt b/app/src/main/java/me/anon/grow/fragment/StatisticsFragment2.kt new file mode 100644 index 00000000..bfe98e19 --- /dev/null +++ b/app/src/main/java/me/anon/grow/fragment/StatisticsFragment2.kt @@ -0,0 +1,1157 @@ +package me.anon.grow.fragment + +import android.content.Context +import android.graphics.Color +import android.graphics.Typeface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.view.ContextThemeWrapper +import androidx.core.graphics.ColorUtils +import androidx.core.view.plusAssign +import androidx.fragment.app.Fragment +import com.github.mikephil.charting.components.* +import com.github.mikephil.charting.data.* +import com.github.mikephil.charting.formatter.ValueFormatter +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet +import com.github.mikephil.charting.utils.MPPointF +import com.google.android.material.chip.Chip +import kotlinx.android.synthetic.main.data_label_stub.view.* +import kotlinx.android.synthetic.main.statistics2_view.* +import me.anon.grow.R +import me.anon.grow.fragment.StatisticsFragment2.template.data +import me.anon.grow.fragment.StatisticsFragment2.template.header +import me.anon.lib.TdsUnit +import me.anon.lib.TempUnit +import me.anon.lib.Unit +import me.anon.lib.ext.* +import me.anon.lib.helper.StatsHelper.formatter +import me.anon.lib.helper.TimeHelper +import me.anon.model.* +import java.lang.Math.abs +import kotlin.math.absoluteValue +import kotlin.math.ceil +import kotlin.math.max +import kotlin.math.min + +/** + * // TODO: Add class description + */ +class StatisticsFragment2 : Fragment() +{ + class StatisticsViewModel( + val selectedTdsUnit: TdsUnit, + val selectedDeliveryUnit: Unit, + val selectedMeasurementUnit: Unit, + val selectedTempUnit: TempUnit, + val plant: Plant + ) + { + class StageDate(var day: Int, var total: Int, var stage: PlantStage) + class StatWrapper(var min: Double? = null, var max: Double? = null, var average: Double? = null) + class AdditiveStat( + var total: Double = 0.0, + var totalAdjusted: Double = 0.0, + var count: Int = 0, + var min: Double = Double.NaN, + var max: Double = Double.NaN + ) + + // stat variables + public val stageChanges by lazy { + plant.getStages().also { + it.toSortedMap(Comparator { first, second -> + (it[first]?.date ?: 0).compareTo(it[second]?.date ?: 0) + }) + } + } + + public val plantStages by lazy { + plant.calculateStageTime().also { + it.remove(PlantStage.HARVESTED) + } + } + + public val aveStageWaters by lazy { + LinkedHashMap>().also { waters -> + waters.putAll(plantStages.keys.map { it }.associateWith { arrayListOf() }) + } + } + + public val additiveStats = LinkedHashMap() + public val additives = hashMapOf>() + public val waterDates = arrayListOf() + public val additiveValues = hashMapOf>() + public val additiveTotalValues = hashMapOf>() + + public val phValues = arrayListOf() + public val phStats = StatWrapper() + + public val runoffValues = arrayListOf() + public val runoffStats = StatWrapper() + + public val tdsValues = hashMapOf>() + public val tdsStats = hashMapOf() + + public val tempValues = ArrayList() + public val tempStats = StatWrapper() + + public var endDate = System.currentTimeMillis() + public var waterDifference = 0L + public var lastWater = 0L + public var totalWater = 0 + public var totalWaterAmount = 0.0 + public var totalFlush = 0 + + public val startDate = plant.plantDate + public val totalDays get() = ((endDate - startDate) / 1000.0) * 0.0000115741 + + init { calculateStats() } + + public fun calculateStats() + { + val tempTempValues = arrayListOf() + val tempPhValues = arrayListOf() + val tempRunoffValues = arrayListOf() + val tempTdsValues = hashMapOf>() + var waterIndex = 0 + plant.actions?.forEach { action -> + when (action) + { + is StageChange -> { + if (action.newStage == PlantStage.HARVESTED) endDate = action.date + } + + is Water -> { + if (lastWater != 0L) waterDifference += abs(action.date - lastWater) + totalWater++ + totalWaterAmount += action.amount ?: 0.0 + lastWater = action.date + + // find the stage change where the date is older than the watering + val sortedStageChange = stageChanges.filterValues { it.date <= action.date }.toSortedMap() + val stage = sortedStageChange.lastKey() + val stageChangeDate = sortedStageChange[stage]?.date ?: 0 + val waterDate = action.date + val stageLength = (waterDate - stageChangeDate).toDays().toInt() + val totalDate = TimeHelper.toDays(action.date - plant.plantDate).toInt() + waterDates.add(StageDate(stageLength, totalDate, stage)) + aveStageWaters.getOrPut(stage, { arrayListOf() }).add(action.date) + + // pH stats + action.ph?.let { + tempPhValues += it + phStats.max = max(phStats.max ?: Double.MIN_VALUE, it) + phStats.min = min(phStats.min ?: Double.MAX_VALUE, it) + + phValues += Entry(waterIndex.toFloat(), it.toFloat()) + } + + // runoff stats + action.runoff?.let { + tempRunoffValues += it + runoffStats.max = max(runoffStats.max ?: Double.MIN_VALUE, it) + runoffStats.min = min(runoffStats.min ?: Double.MAX_VALUE, it) + + runoffValues += Entry(waterIndex.toFloat(), it.toFloat()) + } + + // tds stats + action.tds?.let { tds -> + tds.amount?.let { amount -> + tempTdsValues.getOrPut(tds.type) { arrayListOf() }.add(amount) + tdsStats.getOrPut(tds.type) { StatWrapper() }.apply { + this.max = max(this.max ?: Double.MIN_VALUE, amount) + this.min = min(this.min ?: Double.MAX_VALUE, amount) + } + + tdsValues.getOrPut(tds.type) { arrayListOf() }.add(Entry(waterIndex.toFloat(), amount.toFloat())) + } + } + + // temp stats + action.temp?.let { + tempTempValues += it + tempStats.max = max(tempStats.max ?: Double.MIN_VALUE, it) + tempStats.min = min(tempStats.min ?: Double.MAX_VALUE, it) + + tempValues += Entry(waterIndex.toFloat(), it.toFloat()) + } + + // add additives to pre calculated list + action.additives.forEach { additive -> + if (additive.description != null) + { + additive.amount?.let { amount -> + with (additiveValues) { + val amount = Unit.ML.to(selectedMeasurementUnit, amount) + val entry = Entry(waterIndex.toFloat(), amount.toFloat()) + + val index = keys.map { it.normalise() }.indexOf(additive.description!!.normalise()) + var key = additive.description!! + + if (index > -1) + { + key = keys.toList()[index] + } + + getOrPut(key, { arrayListOf() }).add(entry) + } + + with (additiveTotalValues) { + val totalDelivery = Unit.ML.to(selectedDeliveryUnit, action.amount ?: 1000.0) + val additiveAmount = Unit.ML.to(selectedMeasurementUnit, amount) + + val entry = Entry(waterIndex.toFloat(), Unit.toTwoDecimalPlaces(additiveAmount * totalDelivery).toFloat()) + + val index = keys.map { it.normalise() }.indexOf(additive.description!!.normalise()) + var key = additive.description!! + + if (index > -1) + { + key = keys.toList()[index] + } + + getOrPut(key, { arrayListOf() }).add(entry) + } + } + } + } + + waterIndex++ + additives.getOrPut(action) { arrayListOf() }.addAll(action.additives) + } + + is EmptyAction -> { + if (action.action == Action.ActionName.FLUSH) totalFlush++ + } + } + } + + phStats.average = if (tempPhValues.isNotEmpty()) tempPhValues.average() else null + runoffStats.average = if (tempRunoffValues.isNotEmpty()) tempRunoffValues.average() else null + tempStats.average = if (tempTempValues.isNotEmpty()) tempTempValues.average() else null + tdsStats.forEach { (k, v) -> + tempTdsValues[k]?.let { + tdsStats[k]?.average = if (it.isNotEmpty()) it.average() else null + } + } + + additives.keys.forEach { water -> + additives[water]?.sortedBy { it.description }?.forEach { additive -> + additive.description?.let { key -> + additiveStats.getOrPut(key, { AdditiveStat() }).apply { + total += additive.amount ?: 0.0 + min = min(min.isNaN() T Double.MAX_VALUE ?: min, additive.amount ?: 0.0) + max = max(max.isNaN() T Double.MIN_VALUE ?: max, additive.amount ?: 0.0) + + additiveTotalValues[key]?.let { totalValues -> + var total = 0.0 + totalValues.forEach { entry -> + total += entry.y + } + + totalAdjusted = total + } + + count++ + } + } + } + } + } + } + + sealed class template + { + open class header(var label: String) : template() + open class data(label: String, val data: String) : header(label) + } + + companion object + { + @JvmStatic + public fun newInstance(args: Bundle) = StatisticsFragment2().apply { + this.arguments = args + } + + public fun styleDataset(context: Context, data: LineDataSet, colour: Int) + { + val context = ContextThemeWrapper(context, R.style.AppTheme) + data.valueTextColor = R.attr.colorAccent.resolveColor(context) + data.setCircleColor(R.attr.colorAccent.resolveColor(context)) + data.cubicIntensity = 0.2f + data.lineWidth = 3.0f + data.setDrawCircleHole(true) + data.color = colour + data.setCircleColor(colour) + data.circleRadius = 4.0f + data.setDrawHighlightIndicators(true) + data.isHighlightEnabled = true + data.highlightLineWidth = 2f + data.highLightColor = ColorUtils.setAlphaComponent(colour, 96) + data.setDrawValues(false) + data.valueFormatter = formatter + } + } + + private lateinit var plant: Plant + private lateinit var viewModel: StatisticsViewModel + private val checkedAdditives = setOf() + private val statsColours by lazy { + resources.getStringArray(R.array.stats_colours).map { + Color.parseColor(it) + } + } + + val datesFormatter = object : ValueFormatter() + { + override fun getAxisLabel(value: Float, axis: AxisBase?): String + { + return viewModel.waterDates.getOrNull(value.toInt())?.transform { + "${total}/${day}${getString(stage.printString).toLowerCase()[0]}" + } ?: "" + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? + = inflater.inflate(R.layout.statistics2_view, container, false) + + override fun onActivityCreated(savedInstanceState: Bundle?) + { + super.onActivityCreated(savedInstanceState) + + (savedInstanceState ?: arguments)?.let { + plant = it.getParcelable("plant") as Plant + } + + if (!::plant.isInitialized) return + + val selectedTdsUnit = TdsUnit.getSelectedTdsUnit(requireContext()) + val selectedDeliveryUnit = Unit.getSelectedDeliveryUnit(requireContext()) + val selectedMeasurementUnit = Unit.getSelectedMeasurementUnit(requireContext()) + val selectedTempUnit = TempUnit.getSelectedTemperatureUnit(requireContext()) + + viewModel = StatisticsViewModel( + selectedTdsUnit, + selectedDeliveryUnit, + selectedMeasurementUnit, + selectedTempUnit, + plant + ) + populateGeneralStats() + populateAdditiveStats() + populatePhStats() + populateTdsStats() + populateTempStats() + + activity?.title = getString(R.string.statistics_title) + } + + private fun populateGeneralStats() + { + val statTemplates = arrayListOf