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
+[](https://travis-ci.com/7LPdWcaW/GrowTracker-Android)
+[](https://github.com/7LPdWcaW/GrowTracker-Android/releases/tag/latest)
+[](https://reddit.com/r/growutils)
+[](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.
-[](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)
+[](https://github.com/7LPdWcaW/GrowTracker-Android/releases)
+[](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)
+[
](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
+[](fastlane/metadata/android/en-GB/images/phoneScreenshots/1.png)
+[](fastlane/metadata/android/en-GB/images/phoneScreenshots/1b.png)
+
+
+
+ More screenshots
+
[](fastlane/metadata/android/en-GB/images/phoneScreenshots/install.png)
[](fastlane/metadata/android/en-GB/images/phoneScreenshots/1.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
[](fastlane/metadata/android/en-GB/images/phoneScreenshots/9b.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 
-- Basti B (Weltenesche) - de 
-- Heimen Stoffels (Vistaus) - nl 
-- EmmanuelMess - es 
-- Maxtille - fr 
-- Patrick B (EukalyptusX) - de 
-- Sascha Zenglein (szenglein) - de 
-- Vexatos - de 
-- W Q (williq) - de 
-- 9YbQiuEohUu1 - ru/uk  
+- Chinese (Taiwan) - ; Chief Ndora (chiefndora), codecyang
+- Dutch - ; Heimen Stoffels (Vistaus)
+- French - ; Maxtille, yassine azirem (yassix.well)
+- German - ; Acrylic Boy, Alex (Noxmiles), Basti B (Weltenesche), Patrick B (EukalyptusX), Sascha Zenglein (szenglein), Vexatos, W Q (williq)
+- Hungarian - ;
+- Norwegian Bokmål - ; Syver Stensholt (SuperPotato)
+- Russian - ; 9YbQiuEohUu1
+- Slovenian - ; Klemen Skerbiš (aha999)
+- Spanish - ; EmmanuelMess, Raul Choque (choqueraul123)
+- Ukranian - ; 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()
+
+ stats_container.removeAllViews()
+
+ PlantStage.values().forEach { stage ->
+ if (viewModel.plantStages.containsKey(stage))
+ {
+ viewModel.plantStages[stage]?.let { time ->
+ statTemplates += data(
+ label = "${getString(stage.printString)}:",
+ data = "${TimeHelper.toDays(time).toInt()} ${resources.getQuantityString(R.plurals.time_day, TimeHelper.toDays(time).toInt())}"
+ )
+ }
+ }
+ }
+
+ // total time
+ statTemplates += data(
+ label = getString(R.string.total_time_label),
+ data = "${viewModel.totalDays.formatWhole()} ${resources.getQuantityString(R.plurals.time_day, viewModel.totalDays.toInt())}"
+ )
+
+ statTemplates += header("Water stats")
+
+ // total waters
+ statTemplates += data(
+ label = getString(R.string.total_waters_label),
+ data = "${viewModel.totalWater.formatWhole()}"
+ )
+
+ // total flushes
+ statTemplates += data(
+ label = getString(R.string.total_flushes_label),
+ data = "${viewModel.totalFlush.formatWhole()}"
+ )
+
+ // total water amount
+ statTemplates += data(
+ label = getString(R.string.total_water_amount_label),
+ data = "${Unit.ML.to(viewModel.selectedDeliveryUnit, viewModel.totalWaterAmount).formatWhole()} ${viewModel.selectedDeliveryUnit.label}"
+ )
+
+ // average water amount
+ statTemplates += data(
+ label = getString(R.string.ave_water_amount_label),
+ data = "${Unit.ML.to(viewModel.selectedDeliveryUnit, (viewModel.totalWaterAmount / viewModel.totalWater.toDouble())).formatWhole()} ${viewModel.selectedDeliveryUnit.label}"
+ )
+
+ // ave time between water
+ statTemplates += data(
+ label = getString(R.string.ave_time_between_water_label),
+ data = (TimeHelper.toDays(viewModel.waterDifference) / viewModel.totalWater).let { d ->
+ "${d.formatWhole()} ${resources.getQuantityString(R.plurals.time_day, ceil(d).toInt())}"
+ }
+ )
+
+ // ave water time between stages
+ viewModel.aveStageWaters
+ .toSortedMap(Comparator { first, second -> first.ordinal.compareTo(second.ordinal) })
+ .forEach { (stage, dates) ->
+ if (dates.isNotEmpty())
+ {
+ var dateDifference = dates.last() - dates.first()
+ statTemplates += data(
+ label = getString(R.string.ave_time_stage_label, stage.enString),
+ data = (TimeHelper.toDays(dateDifference) / dates.size).let { d ->
+ "${d.formatWhole()} ${resources.getQuantityString(R.plurals.time_day, ceil(d).toInt())}"
+ }
+ )
+ }
+ }
+
+ renderStats(stats_container, statTemplates)
+
+ // stage chart
+ val labels = arrayOfNulls(viewModel.plantStages.size)
+ val yVals = FloatArray(viewModel.plantStages.size)
+
+ var index = viewModel.plantStages.size - 1
+ for (plantStage in viewModel.plantStages.keys)
+ {
+ yVals[index] = max(TimeHelper.toDays(viewModel.plantStages[plantStage] ?: 0).toFloat(), 1f)
+ labels[index--] = getString(plantStage.printString)
+ }
+
+ val stageEntries = arrayListOf()
+ stageEntries += BarEntry(0f, yVals, viewModel.plantStages.keys.toList().asReversed())
+
+ val stageData = BarDataSet(stageEntries, "")
+ stageData.isHighlightEnabled = false
+ stageData.stackLabels = labels
+ stageData.colors = statsColours
+ stageData.valueTypeface = Typeface.DEFAULT_BOLD
+ stageData.valueTextSize = 10f
+ stageData.valueFormatter = object : ValueFormatter()
+ {
+ override fun getBarStackedLabel(value: Float, stackedEntry: BarEntry?): String
+ {
+ stackedEntry?.let {
+ (it.data as? List)?.let { stages ->
+ val stageIndex = it.yVals.indexOf(value)
+ return "${value.toInt()}${getString(stages[stageIndex].printString)[0].toLowerCase()}"
+ }
+ }
+
+ return super.getBarStackedLabel(value, stackedEntry)
+ }
+ }
+
+ val barData = BarData(stageData)
+
+ stage_chart.data = barData
+ stage_chart.setDrawGridBackground(false)
+ stage_chart.description = null
+ stage_chart.isScaleYEnabled = false
+ stage_chart.setDrawBorders(false)
+ stage_chart.setDrawValueAboveBar(false)
+
+ stage_chart.axisLeft.setDrawGridLines(false)
+ stage_chart.axisLeft.axisMinimum = 0f
+ stage_chart.axisLeft.textColor = R.attr.colorOnSurface.resolveColor(context!!)
+ stage_chart.axisLeft.valueFormatter = object : ValueFormatter()
+ {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String
+ {
+ return "${value.toInt()}${getString(R.string.day_abbr)}"
+ }
+ }
+
+ stage_chart.axisRight.setDrawLabels(false)
+ stage_chart.axisRight.setDrawGridLines(false)
+
+ stage_chart.xAxis.setDrawGridLines(false)
+ stage_chart.xAxis.setDrawAxisLine(false)
+ stage_chart.xAxis.setDrawLabels(false)
+
+ stage_chart.legend.textColor = R.attr.colorOnSurface.resolveColor(context!!).toInt()
+ stage_chart.legend.isWordWrapEnabled = true
+ }
+
+ private fun populateAdditiveStats()
+ {
+ val selectedAdditives = arrayListOf()
+ var totalMax = Double.MIN_VALUE
+ val hasItems = viewModel.additiveValues.size > 0
+ additive_group.isVisible = hasItems
+
+ fun displayStats()
+ {
+ additives_stats_container.removeAllViews()
+
+ selectedAdditives.forEach { name ->
+ viewModel.additiveStats[name]?.let { stat ->
+ val stats = arrayListOf()
+ stats += header(getString(R.string.additive_stat_header, name))
+ stats += data(
+ label = getString(R.string.min),
+ data = "${Unit.ML.to(viewModel.selectedMeasurementUnit, stat.min).formatWhole()} ${viewModel.selectedMeasurementUnit.label}/${viewModel.selectedDeliveryUnit.label}"
+ )
+ stats += data(
+ label = getString(R.string.max),
+ data = "${Unit.ML.to(viewModel.selectedMeasurementUnit, stat.max).formatWhole()} ${viewModel.selectedMeasurementUnit.label}/${viewModel.selectedDeliveryUnit.label}"
+ )
+ stats += data(
+ label = getString(R.string.additive_average_usage_label),
+ data = "${Unit.ML.to(viewModel.selectedMeasurementUnit, stat.total / stat.count.toDouble()).formatWhole()} ${viewModel.selectedMeasurementUnit.label}/${viewModel.selectedDeliveryUnit.label}"
+ )
+ stats += data(
+ label = getString(R.string.additive_usage_count_label),
+ data = "${stat.count}"
+ )
+ stats += data(
+ label = getString(R.string.additive_total_usage_label),
+ data = "${Unit.ML.to(viewModel.selectedMeasurementUnit, stat.totalAdjusted).formatWhole()} ${viewModel.selectedMeasurementUnit.label}"
+ )
+
+ renderStats(additives_stats_container, stats)
+ }
+ }
+ }
+
+ fun displayConcentrationChart()
+ {
+ val dataSets = arrayListOf()
+ var index = 0
+ viewModel.additiveValues.toSortedMap().let {
+ it.forEach { (k, v) ->
+ if (selectedAdditives.contains(k))
+ {
+ dataSets += LineDataSet(v, k).apply {
+ color = statsColours[index]
+ fillColor = color
+ setCircleColor(color)
+ styleDataset(context!!, this, color)
+ }
+ }
+
+ index++
+ if (index >= statsColours.size) index = 0
+ }
+ }
+
+ val lineData = LineData(dataSets)
+
+ additives_concentration_chart.data = lineData
+ additives_concentration_chart.notifyDataSetChanged()
+ additives_concentration_chart.invalidate()
+ }
+
+ fun displayTotalsChart()
+ {
+ val pieData = arrayListOf()
+ val colors = arrayListOf()
+ viewModel.additiveTotalValues.toSortedMap().let { values ->
+ var index = 0
+
+ values.forEach { (k, v) ->
+ if (selectedAdditives.contains(k))
+ {
+ var total = 0.0
+ v.forEach { entry ->
+ total += entry.y
+ }
+
+ pieData += PieEntry(total.toFloat()).apply {
+ colors += statsColours[index]
+ }
+ }
+
+ index++
+ if (index >= statsColours.size) index = 0
+ }
+ }
+
+ additives_count_chart.data = PieData(PieDataSet(pieData, "").apply {
+ this.colors = colors
+ this.valueTextSize = 12f
+ this.valueFormatter = object : ValueFormatter()
+ {
+ override fun getFormattedValue(value: Float): String
+ {
+ return "${value.formatWhole()}${viewModel.selectedMeasurementUnit.label}"
+ }
+ }
+ })
+ additives_count_chart.notifyDataSetChanged()
+ additives_count_chart.invalidate()
+ }
+
+ fun displayOvertimeChart()
+ {
+ val barSets = arrayListOf()
+ val dataSets = arrayListOf()
+ var index = 0
+ val newValues = sortedMapOf>()
+
+ viewModel.additiveTotalValues.toSortedMap().let {
+ it.forEach { (key, entries) ->
+ if (selectedAdditives.contains(key))
+ {
+ val newEntries = arrayListOf()
+ var lastEntry: Entry? = null
+ entries.forEach { entry ->
+ val newEntry = Entry(entry.x, entry.y + (lastEntry?.y ?: 0.0f))
+ newEntries.add(newEntry)
+ lastEntry = newEntry
+ }
+
+ newValues[key] = newEntries
+
+ dataSets += LineDataSet(newValues[key], key).apply {
+ color = statsColours[index]
+ fillColor = color
+ setCircleColor(color)
+ styleDataset(context!!, this, color)
+ }
+ }
+
+ index++
+ if (index >= statsColours.size) index = 0
+ }
+ }
+
+ val stageEntries = viewModel.plantStages.keys.toList().asReversed()
+ viewModel.waterDates.forEachIndexed { additiveIndex, date ->
+ barSets += BarDataSet(arrayListOf(BarEntry(additiveIndex.toFloat(), totalMax.toFloat())), null).apply {
+ color = ColorUtils.setAlphaComponent(statsColours[stageEntries.indexOf(date.stage) % statsColours.size], 127)
+ }
+ }
+
+ val lineData = LineData(dataSets)
+
+ additives_overtime_chart.data = lineData
+ additives_overtime_chart.notifyDataSetChanged()
+ additives_overtime_chart.invalidate()
+ }
+
+ fun refreshCharts()
+ {
+ displayConcentrationChart()
+ displayTotalsChart()
+ displayOvertimeChart()
+ displayStats()
+ }
+
+ viewModel.additiveStats.forEach { (k, v) ->
+ val chip = LayoutInflater.from(context!!).inflate(R.layout.filter_chip_stub, additive_chips_container, false) as Chip
+ chip.text = k
+ chip.isChecked = true
+ chip.setOnCheckedChangeListener { buttonView, isChecked ->
+ if (isChecked) selectedAdditives += k
+ else selectedAdditives -= k
+
+ refreshCharts()
+ }
+
+ selectedAdditives += k
+ additive_chips_container += chip
+ }
+
+ val entries = arrayListOf()
+ viewModel.additiveValues.toSortedMap().let {
+ var index = 0
+ it.forEach { (k, v) ->
+ if (selectedAdditives.contains(k))
+ {
+ entries.add(LegendEntry().apply {
+ label = k
+ formColor = statsColours[index]
+ })
+ }
+
+ index++
+ if (index >= statsColours.size) index = 0
+ }
+ }
+
+ with (additives_concentration_chart) {
+ style()
+
+ marker = object : MarkerView(activity, R.layout.chart_marker)
+ {
+ override fun refreshContent(e: Entry, highlight: Highlight): kotlin.Unit
+ {
+ val color = additives_concentration_chart.data.dataSets[highlight.dataSetIndex].color
+ with (this.findViewById(R.id.content)) {
+ text = "${e.y.formatWhole()} ${viewModel.selectedMeasurementUnit.label}/${viewModel.selectedDeliveryUnit.label}"
+ setTextColor(color)
+ }
+
+ super.refreshContent(e, highlight)
+ }
+
+ override fun getOffset(): MPPointF = MPPointF.getInstance(-(width / 2f), -(height * 1.2f))
+ }
+
+ axisLeft.granularity = 1f
+ axisLeft.valueFormatter = object : ValueFormatter()
+ {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String
+ {
+ return "${value.formatWhole()}${viewModel.selectedMeasurementUnit.label}/${viewModel.selectedDeliveryUnit.label}"
+ }
+ }
+
+ xAxis.valueFormatter = datesFormatter
+
+ legend.setCustom(entries)
+ legend.yOffset = 10f
+ legend.xOffset = 10f
+ }
+
+ with (additives_overtime_chart) {
+ style()
+
+ marker = object : MarkerView(activity, R.layout.chart_marker)
+ {
+ override fun refreshContent(e: Entry, highlight: Highlight): kotlin.Unit
+ {
+ val color = additives_overtime_chart.data.dataSets[highlight.dataSetIndex].color
+ with (this.findViewById(R.id.content)) {
+ text = "${e.y.formatWhole()} ${viewModel.selectedMeasurementUnit.label}"
+ setTextColor(color)
+ }
+
+ super.refreshContent(e, highlight)
+ }
+
+ override fun getOffset(): MPPointF = MPPointF.getInstance(-(width / 2f), -(height * 1.2f))
+ }
+
+ axisLeft.granularity = 1f
+ axisLeft.valueFormatter = object : ValueFormatter()
+ {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String
+ {
+ return "${value.formatWhole()}${viewModel.selectedMeasurementUnit.label}/${viewModel.selectedDeliveryUnit.label}"
+ }
+ }
+
+ xAxis.valueFormatter = datesFormatter
+
+ legend.setCustom(entries)
+ legend.yOffset = 10f
+ legend.xOffset = 10f
+ }
+
+ with (additives_count_chart) {
+ description = null
+ setHoleColor(0x00ffffff)
+ legend.setCustom(entries)
+ legend.form = Legend.LegendForm.CIRCLE
+ legend.textColor = R.attr.colorOnSurface.resolveColor(context!!)
+ legend.isWordWrapEnabled = true
+ }
+
+ refreshCharts()
+ }
+
+ private fun populatePhStats()
+ {
+ val INPUT_PH = R.string.stat_input_ph
+ val RUNOFF_PH = R.string.stat_runoff_ph
+ val AVERAGE_PH = R.string.stat_average_ph
+ val selectedModes = arrayListOf()
+
+ fun refreshCharts()
+ {
+ val sets = arrayListOf()
+
+ if (INPUT_PH in selectedModes)
+ {
+ sets += LineDataSet(viewModel.phValues, getString(R.string.stat_input_ph)).apply {
+ color = statsColours[0]
+ fillColor = color
+ setCircleColor(color)
+ styleDataset(context!!, this, color)
+ }
+
+ if (AVERAGE_PH in selectedModes)
+ {
+ sets += LineDataSet(viewModel.phValues.rollingAverage(), getString(R.string.stat_average_runoff_ph)).apply {
+ color = ColorUtils.blendARGB(statsColours[0], 0xffffffff.toInt(), 0.4f)
+ setDrawCircles(false)
+ setDrawValues(false)
+ setDrawCircleHole(false)
+ setDrawHighlightIndicators(true)
+ cubicIntensity = 1f
+ lineWidth = 2.0f
+ isHighlightEnabled = false
+ }
+ }
+ }
+
+ if (RUNOFF_PH in selectedModes)
+ {
+ sets += LineDataSet(viewModel.runoffValues, getString(R.string.stat_runoff_ph)).apply {
+ color = statsColours[1]
+ fillColor = color
+ setCircleColor(color)
+ styleDataset(context!!, this, color)
+ }
+
+ if (AVERAGE_PH in selectedModes)
+ {
+ sets += LineDataSet(viewModel.runoffValues.rollingAverage(), getString(R.string.stat_average_runoff_ph)).apply {
+ color = ColorUtils.blendARGB(statsColours[1], 0xffffffff.toInt(), 0.4f)
+ setDrawCircles(false)
+ setDrawValues(false)
+ setDrawCircleHole(false)
+ setDrawHighlightIndicators(true)
+ cubicIntensity = 1f
+ lineWidth = 2.0f
+ isHighlightEnabled = false
+ }
+ }
+ }
+
+ input_ph.data = LineData(sets)
+ ph_group.isVisible = input_ph.data.entryCount > 0
+ input_ph.notifyDataSetChanged()
+ input_ph.invalidate()
+ }
+
+ fun displayStats()
+ {
+ ph_stats_container.removeAllViews()
+
+ selectedModes.forEach { mode ->
+ val stats = arrayListOf()
+ stats += header(getString(mode))
+
+ val stat = when (mode)
+ {
+ INPUT_PH -> viewModel.phStats
+ RUNOFF_PH -> viewModel.runoffStats
+ else -> null
+ }
+
+ stat ?: return@forEach
+ stat.min?.let {
+ stats += data(
+ label = getString(R.string.min),
+ data = it.formatWhole()
+ )
+ }
+
+ stat.max?.let {
+ stats += data(
+ label = getString(R.string.max),
+ data = it.formatWhole()
+ )
+ }
+
+ stat.average?.let {
+ stats += data(
+ label = getString(R.string.ave),
+ data = it.formatWhole()
+ )
+ }
+
+ if (stats.size > 1) renderStats(ph_stats_container, stats)
+ }
+ }
+
+ arrayListOf().apply {
+ if (viewModel.phValues.isNotEmpty()) add(INPUT_PH)
+ if (viewModel.runoffValues.isNotEmpty()) add(RUNOFF_PH)
+ if (isNotEmpty()) add(AVERAGE_PH)
+ }.forEach { mode ->
+ val chip = LayoutInflater.from(context!!).inflate(R.layout.filter_chip_stub, ph_chips_container, false) as Chip
+ chip.setText(mode)
+ chip.isChecked = true
+ chip.setOnCheckedChangeListener { buttonView, isChecked ->
+ if (isChecked) selectedModes += mode
+ else selectedModes -= mode
+
+ refreshCharts()
+ displayStats()
+ }
+
+ selectedModes += mode
+ ph_chips_container += chip
+ }
+
+ with (input_ph) {
+ setVisibleYRangeMaximum(max(viewModel.phStats.max?.toFloat() ?: 0.0f, viewModel.runoffStats.max?.toFloat() ?: 0.0f), YAxis.AxisDependency.LEFT)
+ style()
+
+ marker = object : MarkerView(activity, R.layout.chart_marker)
+ {
+ override fun refreshContent(e: Entry, highlight: Highlight): kotlin.Unit
+ {
+ val color = input_ph.data.dataSets[highlight.dataSetIndex].color
+ with (this.findViewById(R.id.content)) {
+ text = e.y.formatWhole()
+ setTextColor(color)
+ }
+
+ super.refreshContent(e, highlight)
+ }
+
+ override fun getOffset(): MPPointF = MPPointF.getInstance(-(width / 2f), -(height * 1.2f))
+ }
+
+ xAxis.valueFormatter = datesFormatter
+ }
+
+ refreshCharts()
+ displayStats()
+ }
+
+ private fun populateTdsStats()
+ {
+ var selectedUnit: TdsUnit = viewModel.selectedTdsUnit
+
+ fun refreshCharts()
+ {
+ val sets = arrayListOf()
+
+ viewModel.tdsValues[selectedUnit]?.let { values ->
+ sets += LineDataSet(values, getString(selectedUnit.strRes)).apply {
+ color = statsColours[viewModel.tdsValues.keys.indexOfFirst { it == selectedUnit }.absoluteValue % statsColours.size]
+ fillColor = color
+ setCircleColor(color)
+ styleDataset(context!!, this, color)
+ }
+
+ sets += LineDataSet(values.rollingAverage(), getString(R.string.stat_average_tds, selectedUnit.label)).apply {
+ color = ColorUtils.blendARGB(statsColours[viewModel.tdsValues.keys.indexOfFirst { it == selectedUnit }.absoluteValue % statsColours.size], 0xffffffff.toInt(), 0.4f)
+ setDrawCircles(false)
+ setDrawValues(false)
+ setDrawCircleHole(false)
+ setDrawHighlightIndicators(true)
+ cubicIntensity = 1f
+ lineWidth = 2.0f
+ isHighlightEnabled = false
+ }
+ }
+
+ tds_chart.data = LineData(sets)
+ tds_group.isVisible = tds_chart.data.entryCount > 0
+ tds_chart.notifyDataSetChanged()
+ tds_chart.fitScreen()
+ tds_chart.invalidate()
+ }
+
+ fun displayStats()
+ {
+ tds_stats_container.removeAllViews()
+
+ viewModel.tdsStats[selectedUnit]?.let { stat ->
+ val stats = arrayListOf()
+ stats += header(getString(selectedUnit.strRes))
+
+ stat.min?.let {
+ stats += data(
+ label = getString(R.string.min),
+ data = it.formatWhole()
+ )
+ }
+
+ stat.max?.let {
+ stats += data(
+ label = getString(R.string.max),
+ data = it.formatWhole()
+ )
+ }
+
+ stat.average?.let {
+ stats += data(
+ label = getString(R.string.ave),
+ data = it.formatWhole()
+ )
+ }
+
+ if (stats.size > 1) renderStats(tds_stats_container, stats)
+ }
+ }
+
+ val values = TdsUnit.values().filter { it in viewModel.tdsValues.keys }
+ if (values.size > 1)
+ {
+ values.forEach { unit ->
+ val chip = LayoutInflater.from(context!!).inflate(R.layout.filter_chip_stub, tds_chips_container, false) as Chip
+ chip.setText(unit.strRes)
+ chip.isCheckable = true
+ chip.id = unit.strRes
+
+ chip.setOnCheckedChangeListener { buttonView, isChecked ->
+ if (isChecked) selectedUnit = unit
+
+ refreshCharts()
+ displayStats()
+ }
+
+ tds_chips_container += chip
+ }
+
+ tds_chips_container.check(selectedUnit.strRes)
+ }
+ else
+ {
+ tds_chips_container.isVisible = false
+ selectedUnit = values.firstOrNull() ?: viewModel.selectedTdsUnit
+ }
+
+ with (tds_chart) {
+ style()
+
+ marker = object : MarkerView(activity, R.layout.chart_marker)
+ {
+ override fun refreshContent(e: Entry, highlight: Highlight): kotlin.Unit
+ {
+ val color = tds_chart.data.dataSets[highlight.dataSetIndex].color
+ with (this.findViewById(R.id.content)) {
+ text = e.y.formatWhole()
+ setTextColor(color)
+ }
+
+ super.refreshContent(e, highlight)
+ }
+
+ override fun getOffset(): MPPointF = MPPointF.getInstance(-(width / 2f), -(height * 1.2f))
+ }
+
+ xAxis.valueFormatter = datesFormatter
+ }
+
+ refreshCharts()
+ displayStats()
+ }
+
+ private fun populateTempStats()
+ {
+ with (temp_chart) {
+ setVisibleYRangeMaximum(viewModel.tempStats.max?.toFloat() ?: 0.0f, YAxis.AxisDependency.LEFT)
+ style()
+
+ axisLeft.valueFormatter = object : ValueFormatter()
+ {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String
+ {
+ return "${value.formatWhole()}°${viewModel.selectedTempUnit.label}"
+ }
+ }
+
+ marker = object : MarkerView(activity, R.layout.chart_marker)
+ {
+ override fun refreshContent(e: Entry, highlight: Highlight): kotlin.Unit
+ {
+ val color = temp_chart.data.dataSets[highlight.dataSetIndex].color
+ with (this.findViewById(R.id.content)) {
+ text = "${e.y.formatWhole()}°${viewModel.selectedTempUnit.label}"
+ setTextColor(color)
+ }
+
+ super.refreshContent(e, highlight)
+ }
+
+ override fun getOffset(): MPPointF = MPPointF.getInstance(-(width / 2f), -(height * 1.2f))
+ }
+
+ xAxis.valueFormatter = datesFormatter
+ }
+
+ val sets = arrayListOf()
+
+ sets += LineDataSet(viewModel.tempValues, getString(R.string.stat_input_ph)).apply {
+ color = statsColours[0]
+ fillColor = color
+ setCircleColor(color)
+ styleDataset(context!!, this, color)
+ }
+
+ sets += LineDataSet(viewModel.tempValues.rollingAverage(), getString(R.string.stat_average_temp)).apply {
+ color = ColorUtils.blendARGB(statsColours[0], 0xffffffff.toInt(), 0.4f)
+ setDrawCircles(false)
+ setDrawValues(false)
+ setDrawCircleHole(false)
+ setDrawHighlightIndicators(true)
+ cubicIntensity = 1f
+ lineWidth = 2.0f
+ isHighlightEnabled = false
+ }
+
+ temp_chart.data = LineData(sets)
+ temp_group.isVisible = temp_chart.data.entryCount > 0
+
+ val stats = arrayListOf()
+ viewModel.tempStats.min?.let {
+ stats += data(
+ label = getString(R.string.min),
+ data = "${it.formatWhole()}°${viewModel.selectedTempUnit.label}"
+ )
+ }
+
+ viewModel.tempStats.max?.let {
+ stats += data(
+ label = getString(R.string.max),
+ data = "${it.formatWhole()}°${viewModel.selectedTempUnit.label}"
+ )
+ }
+
+ viewModel.tempStats.average?.let {
+ stats += data(
+ label = getString(R.string.ave),
+ data = "${it.formatWhole()}°${viewModel.selectedTempUnit.label}"
+ )
+ }
+
+ if (stats.size > 0) renderStats(temp_stats_container, stats)
+ }
+
+ private fun renderStats(container: ViewGroup, templates: ArrayList)
+ {
+ templates.forEach { template ->
+ var dataView = when (template)
+ {
+ is data -> {
+ LayoutInflater.from(activity).inflate(R.layout.data_label_stub, stats_container, false).also {
+ it.label.text = template.label
+ it.data.text = template.data
+ }
+ }
+
+ is header -> {
+ LayoutInflater.from(activity).inflate(R.layout.subtitle_stub, stats_container, false).also {
+ (it as TextView).text = template.label
+ it.setPadding(0, resources.getDimension(R.dimen.padding_16dp).toInt(), 0, 0)
+ }
+ }
+
+ else -> null
+ }
+
+ dataView ?: return
+ container += dataView
+ }
+ }
+}
diff --git a/app/src/main/java/me/anon/grow/fragment/TemperatureDialogFragment.kt b/app/src/main/java/me/anon/grow/fragment/TemperatureDialogFragment.kt
index ea2129d8..8ab6146f 100644
--- a/app/src/main/java/me/anon/grow/fragment/TemperatureDialogFragment.kt
+++ b/app/src/main/java/me/anon/grow/fragment/TemperatureDialogFragment.kt
@@ -61,7 +61,7 @@ class TemperatureDialogFragment(var action: TemperatureChange? = null, val callb
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/ViewPhotosFragment.java b/app/src/main/java/me/anon/grow/fragment/ViewPhotosFragment.java
index beb0e637..ce295c3c 100644
--- a/app/src/main/java/me/anon/grow/fragment/ViewPhotosFragment.java
+++ b/app/src/main/java/me/anon/grow/fragment/ViewPhotosFragment.java
@@ -6,10 +6,12 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.database.Cursor;
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.view.ActionMode;
@@ -23,7 +25,7 @@
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -71,6 +73,8 @@ public class ViewPhotosFragment extends Fragment
private Plant plant;
+ private String tempImagePath = "";
+
public static ViewPhotosFragment newInstance(Bundle arguments)
{
ViewPhotosFragment fragment = new ViewPhotosFragment();
@@ -90,6 +94,7 @@ public static ViewPhotosFragment newInstance(Bundle arguments)
@Override public void onSaveInstanceState(@NonNull Bundle outState)
{
outState.putParcelable("plant", plant);
+ outState.putString("temp_image", tempImagePath);
super.onSaveInstanceState(outState);
}
@@ -103,9 +108,11 @@ public static ViewPhotosFragment newInstance(Bundle arguments)
{
plant = getArguments().getParcelable("plant");
}
- else if (savedInstanceState != null)
+
+ if (savedInstanceState != null)
{
plant = savedInstanceState.getParcelable("plant");
+ tempImagePath = savedInstanceState.getString("temp_image", "");
}
if (plant == null)
@@ -121,7 +128,10 @@ else if (savedInstanceState != null)
{
@Override public void onItemSelected(int totalSelected)
{
- if (action == null) return;
+ if (action == null)
+ {
+ return;
+ }
if (totalSelected == 0)
{
@@ -190,7 +200,7 @@ else if (item.getItemId() == R.id.delete)
File imageFile = new File(image);
folders.add(imageFile.getParentFile().getPath());
- if (imageFile.delete())
+ if (!imageFile.exists() || imageFile.delete())
{
plant.getImages().remove(image);
}
@@ -198,23 +208,7 @@ else if (item.getItemId() == R.id.delete)
for (String folder : folders)
{
- File folderFile = new File(folder);
- if (folderFile.isDirectory())
- {
- String[] list = folderFile.list();
- if (list != null)
- {
- if (list.length == 1 && ".nomedia".equals(list[0]))
- {
- new File(folderFile, ".nomedia").delete();
- }
-
- if (folderFile.list() == null || folderFile.list().length == 0)
- {
- folderFile.delete();
- }
- }
- }
+ cleanupFolder(new File(folder));
}
PlantManager.getInstance().upsert(plant);
@@ -406,12 +400,15 @@ private void setEmpty()
{
new File(path, ".nomedia").createNewFile();
}
- catch (IOException e){}
+ catch (IOException e)
+ {
+ }
File out = new File(path, System.currentTimeMillis() + ".jpg");
Uri photoURI = FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID + ".provider", out);
- plant.getImages().add(out.getAbsolutePath());
+ tempImagePath = out.getAbsolutePath();
+ plant.getImages().add(tempImagePath);
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(intent, 1);
@@ -447,58 +444,81 @@ private void setEmpty()
.show();
}
+ private void cleanupFolder(@Nullable File folderPath)
+ {
+ if (folderPath != null)
+ {
+ String[] list = folderPath.list();
+ if (list != null)
+ {
+ if (list.length == 1 && ".nomedia".equals(list[0]))
+ {
+ new File(folderPath, ".nomedia").delete();
+ }
+
+ if (folderPath.list() == null || folderPath.list().length == 0)
+ {
+ folderPath.delete();
+ }
+ }
+ else
+ {
+ folderPath.delete();
+ }
+ }
+ }
+
@Override public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == 1)
{
if (resultCode == Activity.RESULT_CANCELED)
{
- File imageFile = new File(plant.getImages().get(plant.getImages().size() - 1));
+ File imageFile = new File(tempImagePath);
- if (imageFile.delete())
+ if (!imageFile.exists() || imageFile.delete())
{
plant.getImages().remove(plant.getImages().size() - 1);
}
- File folderFile = imageFile.getParentFile();
- String[] list = folderFile.list();
- if (list != null)
+ cleanupFolder(imageFile.getParentFile());
+ }
+ else
+ {
+ File imageFile = new File(tempImagePath);
+ if (imageFile.exists() && imageFile.length() > 0)
{
- if (list.length == 1 && ".nomedia".equals(list[0]))
+ if (!plant.getImages().contains(imageFile.getAbsolutePath()))
{
- new File(folderFile, ".nomedia").delete();
+ plant.getImages().add(imageFile.getAbsolutePath());
}
+ PlantManager.getInstance().upsert(plant);
- if (folderFile.list() == null || folderFile.list().length == 0)
- {
- folderFile.delete();
- }
+ setAdapter();
+ adapter.notifyDataSetChanged();
+
+ finishPhotoIntent();
}
else
{
- folderFile.delete();
+ plant.getImages().remove(imageFile.getAbsolutePath());
+ cleanupFolder(imageFile.getParentFile());
}
}
- else
- {
- PlantManager.getInstance().upsert(plant);
-
- setAdapter();
- adapter.notifyDataSetChanged();
-
- finishPhotoIntent();
- }
}
else if (requestCode == 3) // choose image from gallery
{
if (resultCode != Activity.RESULT_CANCELED)
{
- if (data == null) return;
+ 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
{
@@ -520,10 +540,31 @@ else if (requestCode == 3) // choose image from gallery
{
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()
@@ -549,6 +590,8 @@ else if (requestCode == 3) // choose image from gallery
private void finishPhotoIntent()
{
+ tempImagePath = "";
+
Intent intent = new Intent();
intent.putExtra("plant", plant);
getActivity().setIntent(intent);
@@ -560,19 +603,31 @@ private void finishPhotoIntent()
{
if (MainApplication.isEncrypted())
{
- ArrayList image = new ArrayList<>();
- image.add(plant.getImages().get(plant.getImages().size() - 1));
- new EncryptTask(getActivity()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, image);
+ try
+ {
+ ArrayList image = new ArrayList<>();
+ image.add(plant.getImages().get(plant.getImages().size() - 1));
+ new EncryptTask(getActivity()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, image);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
}
SnackBar.show(getActivity(), R.string.snackbar_image_added, R.string.snackbar_action_take_another, new SnackBarListener()
{
- @Override public void onSnackBarStarted(Object o){}
- @Override public void onSnackBarFinished(Object o){}
+ @Override public void onSnackBarStarted(Object o)
+ {
+ }
+
+ @Override public void onSnackBarFinished(Object o)
+ {
+ }
@Override public void onSnackBarAction(View v)
{
- onFabPhotoClick(null);
+ onFabPhotoClick(v);
}
});
}
diff --git a/app/src/main/java/me/anon/grow/fragment/WateringFragment.java b/app/src/main/java/me/anon/grow/fragment/WateringFragment.java
index 79d8f4e6..929a03b2 100644
--- a/app/src/main/java/me/anon/grow/fragment/WateringFragment.java
+++ b/app/src/main/java/me/anon/grow/fragment/WateringFragment.java
@@ -36,18 +36,21 @@
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import me.anon.controller.provider.PlantWidgetProvider;
+import me.anon.grow.BaseActivity;
import me.anon.grow.R;
import me.anon.lib.TdsUnit;
import me.anon.lib.TempUnit;
import me.anon.lib.Unit;
import me.anon.lib.Views;
import me.anon.lib.ext.NumberUtilsKt;
+import me.anon.lib.manager.GardenManager;
import me.anon.lib.manager.PlantManager;
import me.anon.lib.manager.ScheduleManager;
import me.anon.model.Action;
import me.anon.model.Additive;
import me.anon.model.FeedingSchedule;
import me.anon.model.FeedingScheduleDate;
+import me.anon.model.Garden;
import me.anon.model.Plant;
import me.anon.model.Tds;
import me.anon.model.Water;
@@ -79,6 +82,7 @@ public class WateringFragment extends Fragment
@Views.InjectView(R.id.notes) private EditText notes;
private int[] plantIndex = {-1};
+ private int gardenIndex = -1;
private int actionIndex = -1;
private ArrayList plants = new ArrayList<>();
private Water water;
@@ -100,10 +104,11 @@ public class WateringFragment extends Fragment
* @param plantIndex If -1, assume new plant
* @return Instantiated details fragment
*/
- public static WateringFragment newInstance(int[] plantIndex, int feedingIndex)
+ public static WateringFragment newInstance(int[] plantIndex, int feedingIndex, int gardenIndex)
{
Bundle args = new Bundle();
args.putIntArray("plant_index", plantIndex);
+ args.putInt("garden_index", gardenIndex);
args.putInt("action_index", feedingIndex);
WateringFragment fragment = new WateringFragment();
@@ -138,12 +143,14 @@ public static WateringFragment newInstance(int[] plantIndex, int feedingIndex)
if (savedInstanceState != null)
{
plantIndex = savedInstanceState.getIntArray("plant_index");
+ gardenIndex = savedInstanceState.getInt("garden_index");
actionIndex = savedInstanceState.getInt("action_index");
water = savedInstanceState.getParcelable("water");
}
else if (getArguments() != null)
{
plantIndex = getArguments().getIntArray("plant_index");
+ gardenIndex = getArguments().getInt("garden_index");
actionIndex = getArguments().getInt("action_index");
if (actionIndex > -1 && plantIndex.length == 1)
@@ -176,6 +183,35 @@ else if (getArguments() != null)
return;
}
+ if (getActivity() instanceof BaseActivity)
+ {
+ Garden garden = null;
+ if (gardenIndex > -1)
+ {
+ garden = GardenManager.getInstance().getGardens().get(gardenIndex);
+ }
+
+ if (plants.size() == 1)
+ {
+ getActivity().setTitle(getString(R.string.feeding_single_title, plants.get(0).getName()));
+ }
+ else
+ {
+ getActivity().setTitle(getString(R.string.feeding_single_title, garden == null ? "" : garden.getName()));
+ String subtitle = "";
+ for (Plant plant : plants)
+ {
+ if (subtitle.length() > 0) subtitle += ", ";
+ subtitle += plant.getName();
+ }
+
+ ((BaseActivity)getActivity()).getSupportActionBar().setSubtitle(subtitle);
+ }
+ }
+
+ reattachFeedingDialogListener((ActionSelectDialogFragment)getFragmentManager().findFragmentByTag("actions"));
+ reattachScheduleDialogListener((FeedingScheduleSelectDialogFragment)getFragmentManager().findFragmentByTag("feeding"));
+ reattachDateDialogListener((DateDialogFragment)getFragmentManager().findFragmentByTag("date"));
setUi();
setHints();
}
@@ -183,6 +219,7 @@ else if (getArguments() != null)
@Override public void onSaveInstanceState(@NonNull Bundle outState)
{
outState.putIntArray("plant_index", plantIndex);
+ outState.putInt("garden_index", gardenIndex);
outState.putInt("action_index", actionIndex);
outState.putParcelable("water", water);
@@ -254,8 +291,26 @@ public int compare(Action o1, Action o2)
}
});
- ActionSelectDialogFragment actionSelectDialogFragment = new ActionSelectDialogFragment(items);
- actionSelectDialogFragment.setOnActionSelectedListener(new ActionSelectDialogFragment.OnActionSelectedListener()
+ ActionSelectDialogFragment actionSelectDialogFragment = ActionSelectDialogFragment.newInstance(items);
+ actionSelectDialogFragment.show(getFragmentManager(), "actions");
+ reattachFeedingDialogListener(actionSelectDialogFragment);
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void showScheduleDialog(FeedingSchedule schedule)
+ {
+ FeedingScheduleSelectDialogFragment feedingScheduleSelectDialogFragment = FeedingScheduleSelectDialogFragment.newInstance(schedule, plants);
+ feedingScheduleSelectDialogFragment.show(getFragmentManager(), "feeding");
+ reattachScheduleDialogListener(feedingScheduleSelectDialogFragment);
+ }
+
+ private void reattachFeedingDialogListener(ActionSelectDialogFragment fragment)
+ {
+ if (fragment != null)
+ {
+ fragment.setOnActionSelectedListener(new ActionSelectDialogFragment.OnActionSelectedListener()
{
@Override public void onActionSelected(Action action)
{
@@ -264,31 +319,55 @@ public int compare(Action o1, Action o2)
setUi();
}
});
- actionSelectDialogFragment.show(getFragmentManager(), "actions");
}
-
- return super.onOptionsItemSelected(item);
}
- private void showScheduleDialog(FeedingSchedule schedule)
+ private void reattachDateDialogListener(DateDialogFragment fragment)
{
- FeedingScheduleSelectDialogFragment feedingScheduleSelectDialogFragment = new FeedingScheduleSelectDialogFragment(schedule, plants.get(0));
- feedingScheduleSelectDialogFragment.setOnFeedingSelectedListener(new FeedingScheduleSelectDialogFragment.OnFeedingSelectedListener()
+ if (fragment != null)
{
- @Override public void onFeedingSelected(FeedingScheduleDate date)
+ fragment.setOnDateSelected(new DateDialogFragment.OnDateSelectedListener()
{
- ArrayList additives = new ArrayList<>();
+ @Override public void onDateSelected(Calendar date)
+ {
+ final DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(getActivity());
+ final DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(getActivity());
+
+ String dateStr = dateFormat.format(date.getTime()) + " " + timeFormat.format(date.getTime());
+ WateringFragment.this.date.setText(dateStr);
+
+ water.setDate(date.getTimeInMillis());
+ onCancelled();
+ }
- for (Additive additive : date.getAdditives())
+ @Override public void onCancelled()
{
- additives.add(new Kryo().copy(additive));
+ getFragmentManager().beginTransaction().remove(fragment).commit();
}
+ });
+ }
+ }
- water.setAdditives(additives);
- populateAdditives();
- }
- });
- feedingScheduleSelectDialogFragment.show(getFragmentManager(), "feeding");
+ private void reattachScheduleDialogListener(FeedingScheduleSelectDialogFragment fragment)
+ {
+ if (fragment != null)
+ {
+ fragment.setOnFeedingSelectedListener(new FeedingScheduleSelectDialogFragment.OnFeedingSelectedListener()
+ {
+ @Override public void onFeedingSelected(FeedingScheduleDate date)
+ {
+ ArrayList additives = new ArrayList<>();
+
+ for (Additive additive : date.getAdditives())
+ {
+ additives.add(new Kryo().copy(additive));
+ }
+
+ water.setAdditives(additives);
+ populateAdditives();
+ }
+ });
+ }
}
private void setHints()
@@ -337,12 +416,13 @@ private void setHints()
phCount++;
}
- if (hint.getTds() != null)
+ if (hint.getTds() != null && hint.getTds().getAmount() != null)
{
- Tds tds = new Tds(hint.getTds().getAmount(), hint.getTds().getType());
+ double amt = hint.getTds().getAmount();
+ Tds tds = new Tds(amt, hint.getTds().getType());
if (hint.getTds().getType() == selectedTdsUnit)
{
- selectedTdsAverage += hint.getTds().getAmount();
+ selectedTdsAverage += amt;
averagePpm.add(tds);
}
}
@@ -374,7 +454,7 @@ private void setHints()
if (!averagePh.isNaN())
{
- waterPh.setHint(String.valueOf(averagePh));
+ waterPh.setHint(NumberUtilsKt.formatWhole(averagePh));
}
if (!selectedTdsAverage.isNaN())
@@ -385,13 +465,13 @@ private void setHints()
}
else
{
- waterPpm.setHint(selectedTdsAverage + " " + selectedTdsUnit.getLabel());
+ waterPpm.setHint(NumberUtilsKt.formatWhole(selectedTdsAverage) + " " + selectedTdsUnit.getLabel());
}
}
if (!averageRunoff.isNaN())
{
- runoffPh.setHint(String.valueOf(averageRunoff));
+ runoffPh.setHint(NumberUtilsKt.formatWhole(averageRunoff));
}
if (!averageAmount.isNaN())
@@ -421,8 +501,6 @@ private void setUi()
date.setText("");
notes.setText("");
- getActivity().setTitle(plants.size() == 1 ? getString(R.string.feeding_single_title, plants.get(0).getName()) : getString(R.string.feeding_multiple_title));
-
Calendar date = Calendar.getInstance();
date.setTimeInMillis(water.getDate());
@@ -436,24 +514,9 @@ private void setUi()
{
@Override public void onClick(View v)
{
- final DateDialogFragment fragment = new DateDialogFragment(water.getDate());
- fragment.setOnDateSelected(new DateDialogFragment.OnDateSelectedListener()
- {
- @Override public void onDateSelected(Calendar date)
- {
- String dateStr = dateFormat.format(date.getTime()) + " " + timeFormat.format(date.getTime());
- WateringFragment.this.date.setText(dateStr);
-
- water.setDate(date.getTimeInMillis());
- onCancelled();
- }
-
- @Override public void onCancelled()
- {
- getFragmentManager().beginTransaction().remove(fragment).commit();
- }
- });
+ final DateDialogFragment fragment = DateDialogFragment.newInstance(water.getDate());
getFragmentManager().beginTransaction().add(fragment, "date").commit();
+ reattachDateDialogListener(fragment);
}
});
@@ -465,9 +528,10 @@ private void setUi()
waterPh.setText(String.valueOf(water.getPh()));
}
- if (water.getTds() != null)
+ if (water.getTds() != null && water.getTds().getAmount() != null)
{
- String ppm = NumberUtilsKt.formatWhole(water.getTds().getAmount());
+ Double amt = water.getTds().getAmount();
+ String ppm = NumberUtilsKt.formatWhole(amt);
waterPpm.setText(ppm);
}
@@ -503,7 +567,7 @@ private void populateAdditives()
if (additive == null || additive.getAmount() == null) continue;
double converted = Unit.ML.to(selectedMeasurementUnit, additive.getAmount());
- String amountStr = converted == Math.floor(converted) ? String.valueOf((int)converted) : String.valueOf(converted);
+ String amountStr = NumberUtilsKt.formatWhole(converted);
amountStr = additive.getDescription() + " - " + amountStr + selectedMeasurementUnit.getLabel() + "/" + selectedDeliveryUnit.getLabel();
maxChars = Math.max(maxChars, amountStr.length());
}
@@ -539,7 +603,7 @@ private void populateAdditives()
totalDelivery = ML.to(selectedDeliveryUnit, totalDelivery);
Double additiveAmount = ML.to(selectedMeasurementUnit, additive.getAmount());
- amountStr = amountStr + " (" + Unit.toTwoDecimalPlaces(additiveAmount * totalDelivery) + selectedMeasurementUnit.getLabel() + " total)";
+ amountStr = amountStr + " (" + NumberUtilsKt.formatWhole(Unit.toTwoDecimalPlaces(additiveAmount * totalDelivery)) + selectedMeasurementUnit.getLabel() + " total)";
}
((TextView)additiveStub).setText(Html.fromHtml(amountStr));
diff --git a/app/src/main/java/me/anon/lib/SnackBar.kt b/app/src/main/java/me/anon/lib/SnackBar.kt
index 645d2610..fa8dd229 100644
--- a/app/src/main/java/me/anon/lib/SnackBar.kt
+++ b/app/src/main/java/me/anon/lib/SnackBar.kt
@@ -97,7 +97,7 @@ class SnackBar
action: (View) -> kotlin.Unit = {}
)
{
- val snackbar = Snackbar.make(context.findViewById(R.id.coordinator), message, length)
+ val snackbar = Snackbar.make(context.findViewById(R.id.coordinator) ?: context.findViewById(R.id.fragment_holder) , message, length)
var actionText = actionText
var action = action
diff --git a/app/src/main/java/me/anon/lib/Unit.java b/app/src/main/java/me/anon/lib/Unit.java
index e794ee65..fd537195 100644
--- a/app/src/main/java/me/anon/lib/Unit.java
+++ b/app/src/main/java/me/anon/lib/Unit.java
@@ -3,9 +3,6 @@
import android.content.Context;
import android.preference.PreferenceManager;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-
/**
* Unit class used for measurement input
*/
@@ -30,7 +27,7 @@ public enum Unit
}
},
- L("l")
+ L("L")
{
@Override public double to(Unit to, double fromValue)
{
@@ -165,7 +162,7 @@ public enum Unit
private String label;
- private Unit(String label)
+ Unit(String label)
{
this.label = label;
}
@@ -177,7 +174,7 @@ public String getLabel()
public static Double toTwoDecimalPlaces(double input)
{
- return Double.isInfinite(input) || Double.isNaN(input) ? 0.0d : new BigDecimal(input).setScale(2, RoundingMode.HALF_EVEN).doubleValue();
+ return Double.isInfinite(input) || Double.isNaN(input) ? 0.0d : input;
}
/**
diff --git a/app/src/main/java/me/anon/lib/export/ExportHelper.kt b/app/src/main/java/me/anon/lib/export/ExportHelper.kt
index 88bd064a..45ccd969 100644
--- a/app/src/main/java/me/anon/lib/export/ExportHelper.kt
+++ b/app/src/main/java/me/anon/lib/export/ExportHelper.kt
@@ -6,18 +6,33 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.Typeface
import android.os.AsyncTask
import android.os.Environment
import android.view.View
import android.view.ViewGroup
+import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.app.NotificationCompat
import androidx.core.content.FileProvider
+import androidx.core.graphics.ColorUtils
+import com.github.mikephil.charting.charts.HorizontalBarChart
import com.github.mikephil.charting.charts.LineChart
+import com.github.mikephil.charting.charts.PieChart
+import com.github.mikephil.charting.components.AxisBase
+import com.github.mikephil.charting.components.LegendEntry
+import com.github.mikephil.charting.data.*
+import com.github.mikephil.charting.formatter.ValueFormatter
+import com.github.mikephil.charting.interfaces.datasets.IBarDataSet
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet
import me.anon.grow.R
+import me.anon.grow.fragment.StatisticsFragment2
import me.anon.lib.TdsUnit
import me.anon.lib.TempUnit
+import me.anon.lib.ext.*
import me.anon.lib.helper.NotificationHelper
import me.anon.lib.helper.StatsHelper
+import me.anon.lib.helper.TimeHelper
import me.anon.model.*
import net.lingala.zip4j.core.ZipFile
import net.lingala.zip4j.model.ZipParameters
@@ -26,8 +41,11 @@ import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileInputStream
+import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList
+import kotlin.math.absoluteValue
+import kotlin.math.max
/**
* // TODO: Add class description
@@ -38,6 +56,12 @@ class ExportHelper(
val includeImages: Boolean = true
)
{
+ private val statsColours by lazy {
+ context.resources.getStringArray(R.array.stats_colours).map {
+ Color.parseColor(it)
+ }
+ }
+
public fun exportPlants(plants: ArrayList)
{
val intent = Intent(context, ExportService::class.java)
@@ -93,11 +117,12 @@ class ExportHelper(
params.compressionLevel = Zip4jConstants.DEFLATE_LEVEL_NORMAL
garden?.let {
- val processor = exportProcessor.newInstance().apply {
- this.selectedDelivery = deliveryUnit
- this.selectedMeasurement = measureUnit
- this.selectedTemp = tempUnit
- this.selectedTds = tdsUnit
+ val processor = exportProcessor.newInstance().also {
+ it.context = context.applicationContext
+ it.selectedDelivery = deliveryUnit
+ it.selectedMeasurement = measureUnit
+ it.selectedTemp = tempUnit
+ it.selectedTds = tdsUnit
}
processor.beginDocument(false)
@@ -120,11 +145,12 @@ class ExportHelper(
}
plants.forEach { plant ->
- val processor = exportProcessor.newInstance().apply {
- this.selectedDelivery = deliveryUnit
- this.selectedMeasurement = measureUnit
- this.selectedTemp = tempUnit
- this.selectedTds = tdsUnit
+ val processor = exportProcessor.newInstance().also {
+ it.context = context.applicationContext
+ it.selectedDelivery = deliveryUnit
+ it.selectedMeasurement = measureUnit
+ it.selectedTemp = tempUnit
+ it.selectedTds = tdsUnit
}
processor.beginDocument()
@@ -137,7 +163,8 @@ class ExportHelper(
processor.printPlantStats(plant)
processor.printPlantActions(plant)
- val imagePaths = arrayListOf()
+ val imagePaths = sortedMapOf>()
+ val stages = plant.getStages()
for (filePath in plant.images!!)
{
try
@@ -150,7 +177,28 @@ class ExportHelper(
fileDate = currentImage.lastModified()
}
- imagePaths.add(zipPathPrefix + "images/" + dateFolder(context, fileDate) + "/" + fileDate + ".jpg")
+ val totalDays = TimeHelper.toDays(Math.abs(fileDate - plant.plantDate)).toInt()
+ var stageChange: StageChange? = null
+ with (stages.keys.iterator())
+ {
+ while (this.hasNext())
+ {
+ val k = next()
+ stages[k]?.let { action ->
+ if (action.date <= fileDate && stageChange == null)
+ {
+ stageChange = action as? StageChange
+ }
+ }
+ }
+ }
+
+ var dateSuffix = ""
+ stageChange?.let { change ->
+ dateSuffix = " ${totalDays}/" + TimeHelper.toDays(fileDate - change.date).toInt() + context.getString(change.newStage.printString)[0].toLowerCase()
+ }
+
+ imagePaths.getOrPut(dateFolder(context, fileDate) + dateSuffix) { arrayListOf() }.add(zipPathPrefix + "images/" + dateFolder(context, fileDate) + "/" + fileDate + ".jpg")
}
catch (e: java.lang.Exception)
{
@@ -160,14 +208,16 @@ class ExportHelper(
processor.printPlantImages(imagePaths)
// do chart stuff
+ val viewModel = StatisticsFragment2.StatisticsViewModel(tdsUnit, deliveryUnit, measureUnit, tempUnit, plant)
val totalWater = plant.actions?.sumBy { if (it is Water) 1 else 0 } ?: 0
- val width = 1024 + (totalWater * 20)
- val height = 512
+ val width = ((1024 + (viewModel.totalDays * 20)) * 1).toInt()
+ val height = (512 * 1).toInt()
- saveTempChart(width, height, plant, zipPathPrefix, outFile)
- saveTdsCharts(width, height, plant, zipPathPrefix, outFile)
- saveInputPhChart(width, height, plant, zipPathPrefix, outFile)
- saveAdditiveChart(width, height, plant, zipPathPrefix, outFile)
+ saveStagesChart(width, height, viewModel, zipPathPrefix, outFile)
+ saveTempChart(width, height, viewModel, zipPathPrefix, outFile)
+ saveTdsCharts(width, height, viewModel, zipPathPrefix, outFile)
+ saveInputPhChart(width, height, viewModel, zipPathPrefix, outFile)
+ saveAdditiveChart(width, height, viewModel, zipPathPrefix, outFile)
processor.printRaw(plant)
processor.endDocument(outFile, zipPathPrefix)
@@ -319,6 +369,15 @@ class ExportHelper(
StatsHelper.setTempData(garden, context, temp, null)
temp.data.setDrawValues(true)
+ with (temp) {
+ legend.textColor = 0xff000000.toInt()
+ axisLeft.textColor = 0xff000000.toInt()
+ xAxis.textColor = 0xff000000.toInt()
+ xAxis.labelCount = 25
+ xAxis.textSize = 8.0f
+ axisLeft.textSize = 8.0f
+ }
+
try
{
val parameters = ZipParameters()
@@ -327,7 +386,7 @@ class ExportHelper(
parameters.isSourceExternalStream = true
val outputStream = ByteArrayOutputStream()
- temp.chartBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
+ temp.scaledBitmap().compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
val stream = ByteArrayInputStream(outputStream.toByteArray())
outZip.addStream(stream, parameters)
@@ -363,6 +422,15 @@ class ExportHelper(
StatsHelper.setHumidityData(garden, context, chart, null)
chart.data.setDrawValues(true)
+ with (chart) {
+ legend.textColor = 0xff000000.toInt()
+ axisLeft.textColor = 0xff000000.toInt()
+ xAxis.textColor = 0xff000000.toInt()
+ xAxis.labelCount = 25
+ xAxis.textSize = 8.0f
+ axisLeft.textSize = 8.0f
+ }
+
try
{
val parameters = ZipParameters()
@@ -371,7 +439,7 @@ class ExportHelper(
parameters.isSourceExternalStream = true
val outputStream = ByteArrayOutputStream()
- chart.chartBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
+ chart.scaledBitmap().compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
val stream = ByteArrayInputStream(outputStream.toByteArray())
outZip.addStream(stream, parameters)
@@ -388,34 +456,99 @@ class ExportHelper(
}
}
- private fun saveTempChart(width: Int, height: Int, plant: Plant, pathPrefix: String, outZip: ZipFile)
+ private fun saveStagesChart(width: Int, height: Int, viewModel: StatisticsFragment2.StatisticsViewModel, pathPrefix: String, outZip: ZipFile)
{
try
{
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
- val temp = LineChart(context)
- temp.setExtraOffsets(30f, 30f, 30f, 30f)
- temp.setPadding(100, 100, 100, 100)
- temp.layoutParams = ViewGroup.LayoutParams(width, height)
- temp.minimumWidth = width
- temp.minimumHeight = height
- temp.measure(widthMeasureSpec, heightMeasureSpec)
- temp.requestLayout()
- temp.layout(0, 0, width, height)
- StatsHelper.setTempData(plant, context, temp, null)
- temp.data.setDrawValues(true)
+ val stagesChart = HorizontalBarChart(context)
+
+ with (stagesChart) {
+ setExtraOffsets(30f, 30f, 30f, 30f)
+ setPadding(100, 100, 100, 100)
+ layoutParams = ViewGroup.LayoutParams(width, height)
+ minimumWidth = width
+ minimumHeight = height
+ measure(widthMeasureSpec, heightMeasureSpec)
+ requestLayout()
+ layout(0, 0, width, height)
+
+ // stage chart
+ val labels = arrayOfNulls(viewModel.plantStages.size)
+ val yVals = FloatArray(viewModel.plantStages.size)
+
+ var index = viewModel.plantStages.size - 1
+ for (plantStage in viewModel.plantStages.keys)
+ {
+ yVals[index] = max(TimeHelper.toDays(viewModel.plantStages[plantStage] ?: 0).toFloat(), 1f)
+ labels[index--] = context.getString(plantStage.printString)
+ }
+
+ val stageEntries = arrayListOf()
+ stageEntries += BarEntry(0f, yVals, viewModel.plantStages.keys.toList().asReversed())
+
+ val stageData = BarDataSet(stageEntries, "")
+ stageData.isHighlightEnabled = false
+ stageData.stackLabels = labels
+ stageData.colors = statsColours
+ stageData.valueTypeface = Typeface.DEFAULT_BOLD
+ stageData.valueTextSize = 10f
+ stageData.valueFormatter = object : ValueFormatter()
+ {
+ override fun getBarStackedLabel(value: Float, stackedEntry: BarEntry?): String
+ {
+ stackedEntry?.let {
+ (it.data as? List)?.let { stages ->
+ val stageIndex = it.yVals.indexOf(value)
+ return "${value.toInt()}${context.getString(stages[stageIndex].printString)[0].toLowerCase()}"
+ }
+ }
+
+ return super.getBarStackedLabel(value, stackedEntry)
+ }
+ }
+
+ val barData = BarData(stageData)
+ data = barData
+ setDrawGridBackground(false)
+ description = null
+ isScaleYEnabled = false
+ setDrawBorders(false)
+ setDrawValueAboveBar(false)
+
+ axisLeft.setDrawGridLines(false)
+ axisLeft.axisMinimum = 0f
+ axisLeft.textColor = 0xff000000.toInt()
+ axisLeft.valueFormatter = object : ValueFormatter()
+ {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String
+ {
+ return "${value.toInt()}${context.getString(R.string.day_abbr)}"
+ }
+ }
+
+ axisRight.setDrawLabels(false)
+ axisRight.setDrawGridLines(false)
+
+ xAxis.setDrawGridLines(false)
+ xAxis.setDrawAxisLine(false)
+ xAxis.setDrawLabels(false)
+
+ legend.textColor = 0xff000000.toInt()
+ legend.isWordWrapEnabled = true
+ }
try
{
val parameters = ZipParameters()
parameters.compressionMethod = Zip4jConstants.COMP_DEFLATE
- parameters.fileNameInZip = pathPrefix + "temp.jpg"
+ parameters.fileNameInZip = pathPrefix + "stages.jpg"
parameters.isSourceExternalStream = true
val outputStream = ByteArrayOutputStream()
- temp.chartBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
+ stagesChart.scaledBitmap().compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
val stream = ByteArrayInputStream(outputStream.toByteArray())
outZip.addStream(stream, parameters)
@@ -432,22 +565,106 @@ class ExportHelper(
}
}
- private fun saveTdsCharts(width: Int, height: Int, plant: Plant, pathPrefix: String, outZip: ZipFile)
+ private fun saveTempChart(width: Int, height: Int, viewModel: StatisticsFragment2.StatisticsViewModel, pathPrefix: String, outZip: ZipFile)
{
- val tdsNames = TreeSet()
- for (action in plant.actions!!)
+ try
{
- if (action is Water && action.tds != null)
+ val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
+ val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
+
+ val temp_chart = LineChart(context)
+ temp_chart.setExtraOffsets(30f, 30f, 30f, 30f)
+ temp_chart.setPadding(100, 100, 100, 100)
+ temp_chart.layoutParams = ViewGroup.LayoutParams(width, height)
+ temp_chart.minimumWidth = width
+ temp_chart.minimumHeight = height
+ temp_chart.measure(widthMeasureSpec, heightMeasureSpec)
+ temp_chart.requestLayout()
+ temp_chart.layout(0, 0, width, height)
+
+ with (temp_chart) {
+ setVisibleYRangeMaximum(viewModel.tempStats.max?.toFloat() ?: 0.0f, com.github.mikephil.charting.components.YAxis.AxisDependency.LEFT)
+ style()
+ legend.textColor = 0xff000000.toInt()
+ axisLeft.textColor = 0xff000000.toInt()
+ xAxis.textColor = 0xff000000.toInt()
+ xAxis.labelCount = 25
+ xAxis.textSize = 8.0f
+ axisLeft.textSize = 8.0f
+
+ axisLeft.valueFormatter = object : ValueFormatter()
+ {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String
+ {
+ return "${value.formatWhole()}°${viewModel.selectedTempUnit.label}"
+ }
+ }
+
+ xAxis.valueFormatter = object : ValueFormatter()
+ {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String
+ {
+ return viewModel.waterDates.getOrNull(value.toInt())?.transform {
+ "${total}/${day}${context.getString(stage.printString).toLowerCase()[0]}"
+ } ?: ""
+ }
+ }
+ }
+
+ val sets = arrayListOf()
+
+ sets += LineDataSet(viewModel.tempValues, context.getString(R.string.stat_input_ph)).apply {
+ color = statsColours[0]
+ fillColor = color
+ setCircleColor(color)
+ styleDataset(context!!, this, color)
+ }
+
+ sets += LineDataSet(viewModel.tempValues.rollingAverage(), context.getString(R.string.stat_average_temp)).apply {
+ color = ColorUtils.blendARGB(statsColours[0], 0xffffffff.toInt(), 0.4f)
+ setDrawCircles(false)
+ setDrawValues(false)
+ setDrawCircleHole(false)
+ setDrawHighlightIndicators(true)
+ cubicIntensity = 1f
+ lineWidth = 2.0f
+ isHighlightEnabled = false
+ }
+
+ temp_chart.data = LineData(sets)
+ temp_chart.data.setDrawValues(true)
+
+ try
{
- tdsNames.add(action.tds!!.type)
+ val parameters = ZipParameters()
+ parameters.compressionMethod = Zip4jConstants.COMP_DEFLATE
+ parameters.fileNameInZip = pathPrefix + "temp.jpg"
+ parameters.isSourceExternalStream = true
+
+ val outputStream = ByteArrayOutputStream()
+ temp_chart.scaledBitmap().compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
+ val stream = ByteArrayInputStream(outputStream.toByteArray())
+ outZip.addStream(stream, parameters)
+
+ stream.close()
}
+ catch (e: Exception)
+ {
+ e.printStackTrace()
+ }
+ }
+ catch (e: Exception)
+ {
+ e.printStackTrace()
}
+ }
+ private fun saveTdsCharts(width: Int, height: Int, viewModel: StatisticsFragment2.StatisticsViewModel, pathPrefix: String, outZip: ZipFile)
+ {
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
- for (tdsName in tdsNames)
- {
+ viewModel.tdsValues.forEach { (name, values) ->
val tds = LineChart(context)
tds.setExtraOffsets(30f, 30f, 30f, 30f)
tds.setPadding(100, 100, 100, 100)
@@ -457,18 +674,57 @@ class ExportHelper(
tds.measure(widthMeasureSpec, heightMeasureSpec)
tds.requestLayout()
tds.layout(0, 0, width, height)
- StatsHelper.setTdsData(plant, context, tds, null, tdsName)
- tds.data.setDrawValues(true)
+
+ with (tds) {
+ style()
+ legend.textColor = 0xff000000.toInt()
+ axisLeft.textColor = 0xff000000.toInt()
+ xAxis.textColor = 0xff000000.toInt()
+ xAxis.labelCount = 25
+ xAxis.textSize = 8.0f
+ axisLeft.textSize = 8.0f
+
+ xAxis.valueFormatter = object : ValueFormatter()
+ {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String
+ {
+ return viewModel.waterDates.getOrNull(value.toInt())?.transform {
+ "${total}/${day}${context.getString(stage.printString).toLowerCase()[0]}"
+ } ?: ""
+ }
+ }
+
+ val sets = arrayListOf()
+ sets += LineDataSet(values, context.getString(name.strRes)).apply {
+ color = statsColours[viewModel.tdsValues.keys.indexOfFirst { it == name }.absoluteValue % statsColours.size]
+ fillColor = color
+ setCircleColor(color)
+ styleDataset(context!!, this, color)
+ }
+ sets += LineDataSet(values.rollingAverage(), context.getString(R.string.stat_average_tds, name.label)).apply {
+ color = ColorUtils.blendARGB(statsColours[viewModel.tdsValues.keys.indexOfFirst { it == name }.absoluteValue % statsColours.size], 0xffffffff.toInt(), 0.4f)
+ setDrawCircles(false)
+ setDrawValues(false)
+ setDrawCircleHole(false)
+ setDrawHighlightIndicators(true)
+ cubicIntensity = 1f
+ lineWidth = 2.0f
+ isHighlightEnabled = false
+ }
+
+ data = LineData(sets)
+ data.setDrawValues(true)
+ }
try
{
val parameters = ZipParameters()
parameters.compressionMethod = Zip4jConstants.COMP_DEFLATE
- parameters.fileNameInZip = pathPrefix + tdsName.enStr + ".jpg"
+ parameters.fileNameInZip = pathPrefix + name.enStr + ".jpg"
parameters.isSourceExternalStream = true
val outputStream = ByteArrayOutputStream()
- tds.chartBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
+ tds.scaledBitmap().compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
val stream = ByteArrayInputStream(outputStream.toByteArray())
outZip.addStream(stream, parameters)
@@ -481,43 +737,104 @@ class ExportHelper(
}
}
- private fun saveInputPhChart(width: Int, height: Int, plant: Plant, pathPrefix: String, outZip: ZipFile)
+ private fun saveInputPhChart(width: Int, height: Int, viewModel: StatisticsFragment2.StatisticsViewModel, pathPrefix: String, outZip: ZipFile)
{
try
{
- val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
- val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
+ fun saveChart(sets: ArrayList, name: String)
+ {
+ val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
+ val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
+
+ val inputPh = LineChart(context)
+ inputPh.setExtraOffsets(30f, 30f, 30f, 30f)
+ inputPh.setPadding(100, 100, 100, 100)
+ inputPh.layoutParams = ViewGroup.LayoutParams(width, height)
+ inputPh.minimumWidth = width
+ inputPh.minimumHeight = height
+ inputPh.measure(widthMeasureSpec, heightMeasureSpec)
+ inputPh.requestLayout()
+ inputPh.layout(0, 0, width, height)
+
+ with (inputPh) {
+ setVisibleYRangeMaximum(max(viewModel.phStats.max?.toFloat() ?: 0.0f, viewModel.runoffStats.max?.toFloat() ?: 0.0f), com.github.mikephil.charting.components.YAxis.AxisDependency.LEFT)
+ style()
+ legend.textColor = 0xff000000.toInt()
+ axisLeft.textColor = 0xff000000.toInt()
+ xAxis.textColor = 0xff000000.toInt()
+ xAxis.labelCount = 25
+ xAxis.textSize = 8.0f
+ axisLeft.textSize = 8.0f
+
+ xAxis.valueFormatter = object : ValueFormatter()
+ {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String
+ {
+ return viewModel.waterDates.getOrNull(value.toInt())?.transform {
+ "${total}/${day}${context.getString(stage.printString).toLowerCase()[0]}"
+ } ?: ""
+ }
+ }
+ }
+ inputPh.data = LineData(sets)
+ inputPh.data.setDrawValues(true)
- val inputPh = LineChart(context)
- inputPh.setExtraOffsets(30f, 30f, 30f, 30f)
- inputPh.setPadding(100, 100, 100, 100)
- inputPh.layoutParams = ViewGroup.LayoutParams(width, height)
- inputPh.minimumWidth = width
- inputPh.minimumHeight = height
- inputPh.measure(widthMeasureSpec, heightMeasureSpec)
- inputPh.requestLayout()
- inputPh.layout(0, 0, width, height)
- StatsHelper.setInputData(plant, context, inputPh, null)
- inputPh.data.setDrawValues(true)
+ try
+ {
+ val parameters = ZipParameters()
+ parameters.compressionMethod = Zip4jConstants.COMP_DEFLATE
+ parameters.fileNameInZip = "$pathPrefix$name.jpg"
+ parameters.isSourceExternalStream = true
- try
- {
- val parameters = ZipParameters()
- parameters.compressionMethod = Zip4jConstants.COMP_DEFLATE
- parameters.fileNameInZip = pathPrefix + "input-ph.jpg"
- parameters.isSourceExternalStream = true
+ val outputStream = ByteArrayOutputStream()
+ inputPh.scaledBitmap().compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
+ val stream = ByteArrayInputStream(outputStream.toByteArray())
+ outZip.addStream(stream, parameters)
- val outputStream = ByteArrayOutputStream()
- inputPh.chartBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
- val stream = ByteArrayInputStream(outputStream.toByteArray())
- outZip.addStream(stream, parameters)
+ stream.close()
+ }
+ catch (e: Exception)
+ {
+ e.printStackTrace()
+ }
+ }
- stream.close()
+ val sets = arrayListOf()
+ sets += LineDataSet(viewModel.phValues, context.getString(R.string.stat_input_ph)).apply {
+ color = statsColours[0]
+ fillColor = color
+ setCircleColor(color)
+ styleDataset(context!!, this, color)
}
- catch (e: Exception)
- {
- e.printStackTrace()
+ sets += LineDataSet(viewModel.phValues.rollingAverage(), context.getString(R.string.stat_average_ph)).apply {
+ color = ColorUtils.blendARGB(statsColours[0], 0xffffffff.toInt(), 0.4f)
+ setDrawCircles(false)
+ setDrawValues(false)
+ setDrawCircleHole(false)
+ setDrawHighlightIndicators(true)
+ cubicIntensity = 1f
+ lineWidth = 2.0f
+ isHighlightEnabled = false
}
+ saveChart(sets, "input-ph")
+ sets.clear()
+ sets += LineDataSet(viewModel.runoffValues, context.getString(R.string.stat_runoff_ph)).apply {
+ color = statsColours[1]
+ fillColor = color
+ setCircleColor(color)
+ styleDataset(context!!, this, color)
+ }
+ sets += LineDataSet(viewModel.runoffValues.rollingAverage(), context.getString(R.string.stat_average_runoff_ph)).apply {
+ color = ColorUtils.blendARGB(statsColours[1], 0xffffffff.toInt(), 0.4f)
+ setDrawCircles(false)
+ setDrawValues(false)
+ setDrawCircleHole(false)
+ setDrawHighlightIndicators(true)
+ cubicIntensity = 1f
+ lineWidth = 2.0f
+ isHighlightEnabled = false
+ }
+ saveChart(sets, "runoff-ph")
}
catch (e: Exception)
{
@@ -525,54 +842,337 @@ class ExportHelper(
}
}
- private fun saveAdditiveChart(width: Int, height: Int, plant: Plant, pathPrefix: String, outZip: ZipFile)
+ private fun saveAdditiveChart(width: Int, height: Int, viewModel: StatisticsFragment2.StatisticsViewModel, pathPrefix: String, outZip: ZipFile)
{
- try
+ val entries = arrayListOf()
+ viewModel.additiveValues.toSortedMap().let {
+ var index = 0
+ it.forEach { (k, v) ->
+ entries.add(LegendEntry().apply {
+ label = k
+ formColor = statsColours[index]
+ })
+
+ index++
+ if (index >= statsColours.size) index = 0
+ }
+ }
+
+ fun displayConcentrationChart()
{
- val additiveNames = hashSetOf()
- plant.actions?.filter { it is Water }?.forEach {
- additiveNames.addAll((it as Water).additives.map { it.description ?: "" })
+ val dataSets = arrayListOf()
+ var index = 0
+ viewModel.additiveValues.toSortedMap().let {
+ it.forEach { (k, v) ->
+ dataSets += LineDataSet(v, k).apply {
+ color = statsColours[index]
+ fillColor = color
+ setCircleColor(color)
+ styleDataset(context!!, this, color)
+ }
+
+ index++
+ if (index >= statsColours.size) index = 0
+ }
}
- additiveNames.removeAll { it == "" }
- val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
- val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
+ fun saveChart(lineData: LineData, fileName: String)
+ {
+ try
+ {
+ val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
+ val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
+
+ val additives = LineChart(context)
+ with (additives) {
+ setExtraOffsets(30f, 30f, 30f, 30f)
+ setPadding(100, 100, 100, 100)
+ layoutParams = ViewGroup.LayoutParams(width, height)
+ minimumWidth = width
+ minimumHeight = height
+ measure(widthMeasureSpec, heightMeasureSpec)
+ requestLayout()
+ layout(0, 0, width, height)
+ style()
+ legend.textColor = 0xff000000.toInt()
+ axisLeft.textColor = 0xff000000.toInt()
+ xAxis.textColor = 0xff000000.toInt()
+ xAxis.labelCount = 25
+ xAxis.textSize = 8.0f
+ axisLeft.textSize = 8.0f
+
+ axisLeft.granularity = 1f
+ axisLeft.valueFormatter = object : ValueFormatter()
+ {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String
+ {
+ return "${value.formatWhole()}${viewModel.selectedMeasurementUnit.label}/${viewModel.selectedDeliveryUnit.label}"
+ }
+ }
+
+ xAxis.valueFormatter = object : ValueFormatter()
+ {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String
+ {
+ return viewModel.waterDates.getOrNull(value.toInt())?.transform {
+ "${total}/${day}${context.getString(stage.printString).toLowerCase()[0]}"
+ } ?: ""
+ }
+ }
+
+ legend.isWordWrapEnabled = true
+ legend.setCustom(entries)
+ data = lineData
+ data.setDrawValues(true)
+ }
+
+ try
+ {
+ val parameters = ZipParameters()
+ parameters.compressionMethod = Zip4jConstants.COMP_DEFLATE
+ parameters.fileNameInZip = "$pathPrefix$fileName.jpg"
+ parameters.isSourceExternalStream = true
+
+ val outputStream = ByteArrayOutputStream()
+ additives.scaledBitmap().compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
+ val stream = ByteArrayInputStream(outputStream.toByteArray())
+ outZip.addStream(stream, parameters)
+
+ stream.close()
+ }
+ catch (e: Exception)
+ {
+ e.printStackTrace()
+ }
+ }
+ catch (e: java.lang.Exception)
+ {
+ e.printStackTrace()
+ }
+ }
+
+ saveChart(LineData(dataSets), "additives")
+
+ dataSets.forEach { set ->
+ saveChart(LineData(set), set.label.normalise())
+ }
+ }
+
+ fun displayTotalsChart()
+ {
+ val pieData = arrayListOf()
+ val colors = arrayListOf()
+ viewModel.additiveTotalValues.toSortedMap().let { values ->
+ var index = 0
+
+ values.forEach { (k, v) ->
+ var total = 0.0
+ v.forEach { entry ->
+ total += entry.y
+ }
+
+ pieData += PieEntry(total.toFloat()).apply {
+ colors += statsColours[index]
+ }
- val additives = LineChart(context)
- additives.setExtraOffsets(30f, 30f, 30f, 30f)
- additives.setPadding(100, 100, 100, 100)
- additives.layoutParams = ViewGroup.LayoutParams(width, height)
- additives.minimumWidth = width
- additives.minimumHeight = height
- additives.measure(widthMeasureSpec, heightMeasureSpec)
- additives.requestLayout()
- additives.layout(0, 0, width, height)
- StatsHelper.setAdditiveData(plant, context, additives, additiveNames)
- additives.data.setDrawValues(true)
+ index++
+ if (index >= statsColours.size) index = 0
+ }
+ }
try
{
- val parameters = ZipParameters()
- parameters.compressionMethod = Zip4jConstants.COMP_DEFLATE
- parameters.fileNameInZip = pathPrefix + "additives.jpg"
- parameters.isSourceExternalStream = true
+ val width = 1024
+ val height = 1024
+ val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
+ val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
+
+ val additives = PieChart(context)
+ with (additives) {
+ layoutParams = ViewGroup.LayoutParams(width, height)
+ minimumWidth = width
+ minimumHeight = width
+ measure(widthMeasureSpec, heightMeasureSpec)
+ requestLayout()
+ layout(0, 0, width, width)
+
+ description = null
+ setHoleColor(0x00ffffff)
+ legend.setCustom(entries)
+ legend.form = com.github.mikephil.charting.components.Legend.LegendForm.CIRCLE
+ legend.textColor = 0xff000000.toInt()
+ legend.isWordWrapEnabled = true
+
+ data = PieData(PieDataSet(pieData, "").apply {
+ this.colors = colors
+ this.valueTextSize = 12f
+ this.valueFormatter = object : ValueFormatter()
+ {
+ override fun getFormattedValue(value: Float): String
+ {
+ return "${value.formatWhole()}${viewModel.selectedMeasurementUnit.label}"
+ }
+ }
+ })
+ }
- val outputStream = ByteArrayOutputStream()
- additives.chartBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
- val stream = ByteArrayInputStream(outputStream.toByteArray())
- outZip.addStream(stream, parameters)
+ try
+ {
+ val parameters = ZipParameters()
+ parameters.compressionMethod = Zip4jConstants.COMP_DEFLATE
+ parameters.fileNameInZip = pathPrefix + "total-additives.jpg"
+ parameters.isSourceExternalStream = true
- stream.close()
+ val outputStream = ByteArrayOutputStream()
+ additives.chartBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
+ val stream = ByteArrayInputStream(outputStream.toByteArray())
+ outZip.addStream(stream, parameters)
+
+ stream.close()
+ }
+ catch (e: Exception)
+ {
+ e.printStackTrace()
+ }
}
- catch (e: Exception)
+ catch (e: java.lang.Exception)
{
e.printStackTrace()
}
}
- catch (e: java.lang.Exception)
+
+ fun displayOvertimeChart()
{
- e.printStackTrace()
+ val barSets = arrayListOf()
+ val dataSets = arrayListOf()
+ var index = 0
+ val newValues = sortedMapOf>()
+
+ viewModel.additiveTotalValues.toSortedMap().let {
+ it.forEach { (key, entries) ->
+ val newEntries = arrayListOf()
+ var lastEntry: Entry? = null
+ entries.forEach { entry ->
+ val newEntry = Entry(entry.x, entry.y + (lastEntry?.y ?: 0.0f))
+ newEntries.add(newEntry)
+ lastEntry = newEntry
+ }
+
+ newValues[key] = newEntries
+
+ dataSets += LineDataSet(newValues[key], key).apply {
+ color = statsColours[index]
+ fillColor = color
+ setCircleColor(color)
+ styleDataset(context!!, this, color)
+ }
+
+ index++
+ if (index >= statsColours.size) index = 0
+ }
+ }
+
+ val lineData = LineData(dataSets)
+ try
+ {
+ val height = height * 2
+ val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
+ val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
+
+ val additives = LineChart(context)
+ with (additives) {
+ setExtraOffsets(30f, 30f, 30f, 30f)
+ setPadding(100, 100, 100, 100)
+ layoutParams = ViewGroup.LayoutParams(width, height)
+ minimumWidth = width
+ minimumHeight = height
+ measure(widthMeasureSpec, heightMeasureSpec)
+ requestLayout()
+ layout(0, 0, width, height)
+ style()
+ legend.textColor = 0xff000000.toInt()
+ axisLeft.textColor = 0xff000000.toInt()
+ xAxis.textColor = 0xff000000.toInt()
+ axisLeft.labelCount = 25
+ xAxis.labelCount = 25
+ xAxis.textSize = 8.0f
+ axisLeft.textSize = 8.0f
+ axisLeft.axisMinimum = 0.0f
+
+ axisLeft.granularity = 1f
+ axisLeft.valueFormatter = object : ValueFormatter()
+ {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String
+ {
+ return "${value.formatWhole()}${viewModel.selectedMeasurementUnit.label}/${viewModel.selectedDeliveryUnit.label}"
+ }
+ }
+
+ xAxis.valueFormatter = object : ValueFormatter()
+ {
+ override fun getAxisLabel(value: Float, axis: AxisBase?): String
+ {
+ return viewModel.waterDates.getOrNull(value.toInt())?.transform {
+ "${total}/${day}${context.getString(stage.printString).toLowerCase()[0]}"
+ } ?: ""
+ }
+ }
+
+ legend.setCustom(entries)
+ legend.isWordWrapEnabled = true
+ legend.yOffset = 10f
+ legend.xOffset = 10f
+ data = lineData
+ data.setDrawValues(true)
+ }
+
+ try
+ {
+ val parameters = ZipParameters()
+ parameters.compressionMethod = Zip4jConstants.COMP_DEFLATE
+ parameters.fileNameInZip = pathPrefix + "additives-over-time.jpg"
+ parameters.isSourceExternalStream = true
+
+ val outputStream = ByteArrayOutputStream()
+ additives.scaledBitmap().compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
+ val stream = ByteArrayInputStream(outputStream.toByteArray())
+ outZip.addStream(stream, parameters)
+
+ stream.close()
+ }
+ catch (e: Exception)
+ {
+ e.printStackTrace()
+ }
+ }
+ catch (e: java.lang.Exception)
+ {
+ e.printStackTrace()
+ }
}
+
+ displayConcentrationChart()
+ displayTotalsChart()
+ displayOvertimeChart()
+ }
+
+ private 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 = StatsHelper.formatter
}
companion object
@@ -586,8 +1186,7 @@ class ExportHelper(
@JvmStatic
fun dateFolder(context: Context, timestamp: Long): String
{
- val dateFormat = android.text.format.DateFormat.getDateFormat(context)
- return dateFormat.format(Date(timestamp)).replace("[^0-9]".toRegex(), "-")
+ return SimpleDateFormat("yyyy-MM-dd").format(Date(timestamp)).replace("[^0-9]".toRegex(), "-")
}
}
}
diff --git a/app/src/main/java/me/anon/lib/export/ExportProcessor.kt b/app/src/main/java/me/anon/lib/export/ExportProcessor.kt
index 527b444e..39e6e5d0 100644
--- a/app/src/main/java/me/anon/lib/export/ExportProcessor.kt
+++ b/app/src/main/java/me/anon/lib/export/ExportProcessor.kt
@@ -1,17 +1,20 @@
package me.anon.lib.export
+import android.content.Context
import me.anon.lib.TdsUnit
import me.anon.lib.TempUnit
import me.anon.lib.Unit
import me.anon.model.Garden
import me.anon.model.Plant
import net.lingala.zip4j.core.ZipFile
+import java.util.*
/**
* Defines methods to interact with when processing the export data
*/
open abstract class ExportProcessor
{
+ public var context: Context? = null
public var selectedTds: TdsUnit? = null
public var selectedMeasurement: Unit? = null
public var selectedDelivery: Unit? = null
@@ -24,7 +27,7 @@ open abstract class ExportProcessor
open abstract fun printPlantStages(plant: Plant)
open abstract fun printPlantStats(plant: Plant)
open abstract fun printPlantActions(plant: Plant)
- open abstract fun printPlantImages(arrayList: ArrayList)
+ open abstract fun printPlantImages(map: SortedMap>)
open abstract fun printGardenDetails(garden: Garden)
open abstract fun printGardenStats(garden: Garden)
diff --git a/app/src/main/java/me/anon/lib/export/HtmlProcessor.kt b/app/src/main/java/me/anon/lib/export/HtmlProcessor.kt
index 37c14887..bb0e426d 100644
--- a/app/src/main/java/me/anon/lib/export/HtmlProcessor.kt
+++ b/app/src/main/java/me/anon/lib/export/HtmlProcessor.kt
@@ -3,6 +3,7 @@ package me.anon.lib.export
import me.anon.model.Garden
import me.anon.model.Plant
import net.lingala.zip4j.core.ZipFile
+import java.util.*
/**
* // TODO: Add class description
@@ -39,7 +40,7 @@ class HtmlProcessor : ExportProcessor()
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
- override fun printPlantImages(arrayList: ArrayList)
+ override fun printPlantImages(map: SortedMap>)
{
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
diff --git a/app/src/main/java/me/anon/lib/export/MarkdownProcessor.kt b/app/src/main/java/me/anon/lib/export/MarkdownProcessor.kt
index d9818299..5afa554a 100644
--- a/app/src/main/java/me/anon/lib/export/MarkdownProcessor.kt
+++ b/app/src/main/java/me/anon/lib/export/MarkdownProcessor.kt
@@ -1,9 +1,13 @@
package me.anon.lib.export
+import kotlinx.android.synthetic.main.statistics2_view.*
+import me.anon.grow.R
+import me.anon.grow.fragment.StatisticsFragment2
import me.anon.lib.TdsUnit
import me.anon.lib.TempUnit
import me.anon.lib.Unit
import me.anon.lib.ext.formatWhole
+import me.anon.lib.ext.normalise
import me.anon.lib.helper.MoshiHelper
import me.anon.lib.helper.StatsHelper
import me.anon.lib.helper.TimeHelper
@@ -17,6 +21,7 @@ import java.io.ByteArrayInputStream
import java.sql.Timestamp
import java.util.*
import kotlin.collections.HashMap
+import kotlin.math.ceil
/**
* // TODO: Add class description
@@ -139,118 +144,210 @@ class MarkdownProcessor : ExportProcessor()
documentBuilder.append(NEW_LINE + NEW_LINE)
}
+
+ documentBuilder.append("").append(NEW_LINE + NEW_LINE)
}
override fun printPlantStats(plant: Plant)
{
- val startDate = plant.plantDate
- var endDate = System.currentTimeMillis()
- var waterDifference = 0L
- var lastWater = 0L
- var totalWater = 0
- var totalWaterUsed = 0.0
- var totalFlush = 0
- val additives = HashMap()
- val tdsNames = TreeSet()
-
- plant.actions?.forEach { action ->
- if (action is StageChange)
- {
- if (action.newStage === PlantStage.HARVESTED)
- {
- endDate = action.date
+ val viewModel = StatisticsFragment2.StatisticsViewModel(
+ selectedTds ?: throw Exception(),
+ selectedDelivery ?: throw Exception(),
+ selectedMeasurement ?: throw Exception(),
+ selectedTemp ?: throw Exception(),
+ plant
+ )
+
+ fun generateGeneralsStats()
+ {
+ documentBuilder.append("## General Stats")
+ .append(NEW_LINE + NEW_LINE)
+
+ // total time
+ documentBuilder.append("**").append(context!!.getString(R.string.total_time_label)).append("** ")
+ .append("${viewModel.totalDays.formatWhole()} ${context!!.resources.getQuantityString(R.plurals.time_day, viewModel.totalDays.toInt())}")
+ .append(NEW_LINE + NEW_LINE)
+
+ documentBuilder.append("## Water stats")
+ .append(NEW_LINE + NEW_LINE)
+
+ // total waters
+ documentBuilder.append("**").append(context!!.getString(R.string.total_waters_label)).append("** ")
+ .append("${viewModel.totalWater.formatWhole()}")
+ .append(NEW_LINE + NEW_LINE)
+
+ // total flushes
+ documentBuilder.append("**").append(context!!.getString(R.string.total_flushes_label)).append("** ")
+ .append("${viewModel.totalFlush.formatWhole()}")
+ .append(NEW_LINE + NEW_LINE)
+
+ // total water amount
+ documentBuilder.append("**").append(context!!.getString(R.string.total_water_amount_label)).append("** ")
+ .append("${Unit.ML.to(viewModel.selectedDeliveryUnit, viewModel.totalWaterAmount).formatWhole()} ${viewModel.selectedDeliveryUnit.label}")
+ .append(NEW_LINE + NEW_LINE)
+
+ // average water amount
+ documentBuilder.append("**").append(context!!.getString(R.string.ave_water_amount_label)).append("** ")
+ .append("${Unit.ML.to(viewModel.selectedDeliveryUnit, (viewModel.totalWaterAmount / viewModel.totalWater.toDouble())).formatWhole()} ${viewModel.selectedDeliveryUnit.label}")
+ .append(NEW_LINE + NEW_LINE)
+
+ // ave time between water
+ documentBuilder.append("**").append(context!!.getString(R.string.ave_time_between_water_label)).append("** ")
+ documentBuilder.append((TimeHelper.toDays(viewModel.waterDifference) / viewModel.totalWater).let { d ->
+ "${d.formatWhole()} ${context!!.resources.getQuantityString(R.plurals.time_day, ceil(d).toInt())}"
+ }).append(NEW_LINE + NEW_LINE)
+
+ // ave water time between stages
+ viewModel.aveStageWaters
+ .toSortedMap(Comparator { first, second -> first.ordinal.compareTo(second.ordinal) })
+ .forEach { (stage, dates) ->
+ if (dates.isNotEmpty())
+ {
+ var dateDifference = dates.last() - dates.first()
+ documentBuilder.append("**").append(context!!.getString(R.string.ave_time_stage_label, stage.enString)).append("** ")
+ documentBuilder.append((TimeHelper.toDays(dateDifference) / dates.size).let { d ->
+ "${d.formatWhole()} ${context!!.resources.getQuantityString(R.plurals.time_day, ceil(d).toInt())}"
+ }).append(NEW_LINE + NEW_LINE)
+ }
}
+ }
+
+ fun generateAdditiveStats()
+ {
+ documentBuilder.append("## Additive stats").append(NEW_LINE + NEW_LINE)
+
+ // add graph
+
+ documentBuilder.append("").append(NEW_LINE + NEW_LINE)
+ documentBuilder.append("").append(NEW_LINE + NEW_LINE)
+ documentBuilder.append("").append(NEW_LINE + NEW_LINE)
+
+ viewModel.additiveStats.forEach { (key, stat) ->
+ documentBuilder.append("### ").append(context!!.getString(R.string.additive_stat_header, key))
+ .append(NEW_LINE + NEW_LINE)
+
+ documentBuilder.append("}.jpg)").append(NEW_LINE + NEW_LINE)
+
+ documentBuilder.append("**").append(context!!.getString(R.string.min)).append("** ")
+ .append("${Unit.ML.to(viewModel.selectedMeasurementUnit, stat.min).formatWhole()} ${viewModel.selectedMeasurementUnit.label}/${viewModel.selectedDeliveryUnit.label}")
+ .append(NEW_LINE + NEW_LINE)
+ documentBuilder.append("**").append(context!!.getString(R.string.max)).append("** ")
+ .append("${Unit.ML.to(viewModel.selectedMeasurementUnit, stat.max).formatWhole()} ${viewModel.selectedMeasurementUnit.label}/${viewModel.selectedDeliveryUnit.label}")
+ .append(NEW_LINE + NEW_LINE)
+ documentBuilder.append("**").append(context!!.getString(R.string.additive_average_usage_label)).append("** ")
+ .append("${Unit.ML.to(viewModel.selectedMeasurementUnit, stat.total / stat.count.toDouble()).formatWhole()} ${viewModel.selectedMeasurementUnit.label}/${viewModel.selectedDeliveryUnit.label}")
+ .append(NEW_LINE + NEW_LINE)
+ documentBuilder.append("**").append(context!!.getString(R.string.additive_usage_count_label)).append("** ")
+ .append("${stat.count}")
+ .append(NEW_LINE + NEW_LINE)
+ documentBuilder.append("**").append(context!!.getString(R.string.additive_total_usage_label)).append("** ")
+ .append("${Unit.ML.to(viewModel.selectedMeasurementUnit, stat.totalAdjusted).formatWhole()} ${viewModel.selectedMeasurementUnit.label}")
+ .append(NEW_LINE + NEW_LINE)
}
+ }
- if (action is Water)
- {
- if (lastWater != 0L)
+ fun generatePhStats()
+ {
+ arrayListOf(viewModel.phStats, viewModel.runoffStats).forEach { stat ->
+ documentBuilder.append("## ").append(context!!.getString(when (stat)
{
- waterDifference += Math.abs(action.date - lastWater)
+ viewModel.phStats -> R.string.stat_input_ph
+ viewModel.runoffStats -> R.string.stat_runoff_ph
+ else -> throw Exception()
+ }))
+ .append(NEW_LINE + NEW_LINE)
+
+ stat.min?.let {
+ documentBuilder.append("**").append(context!!.getString(R.string.min)).append("** ")
+ documentBuilder.append(it.formatWhole())
+ .append(NEW_LINE + NEW_LINE)
}
- totalWaterUsed += action.amount ?: 0.0
-
- val actionAdditives = action.additives
- for (additive in actionAdditives)
- {
- val delivery: Double = action.amount ?: 1.0
-
- if (!additives.containsKey(additive.description))
- {
- additives.put(additive.description ?: "", 0.0)
- }
-
- val totalDelivery = Unit.ML.to(selectedDelivery, delivery)
- val additiveAmount = Unit.ML.to(selectedMeasurement, additive.amount!!)
+ stat.max?.let {
+ documentBuilder.append("**").append(context!!.getString(R.string.max)).append("** ")
+ documentBuilder.append(it.formatWhole())
+ .append(NEW_LINE + NEW_LINE)
+ }
- additives.put(additive.description ?: "", additives[additive.description]!! + (additiveAmount * totalDelivery))
+ stat.average?.let {
+ documentBuilder.append("**").append(context!!.getString(R.string.ave)).append("** ")
+ documentBuilder.append(it.formatWhole())
+ .append(NEW_LINE + NEW_LINE)
}
- if (action.tds != null)
+ val name = when (stat)
{
- tdsNames.add(action.tds!!.type)
+ viewModel.phStats -> "input-ph"
+ viewModel.runoffStats -> "runoff-ph"
+ else -> throw Exception()
}
- totalWater++
- lastWater = action.date
+ documentBuilder.append("").append(NEW_LINE + NEW_LINE)
}
+ }
- if (action is EmptyAction && action.action === Action.ActionName.FLUSH)
- {
- totalFlush++
+ fun generateTdsStats()
+ {
+ viewModel.tdsStats.forEach { (key, stat) ->
+ if (stat.average?.isNaN() == false)
+ {
+ documentBuilder.append("## ").append(context!!.getString(key.strRes))
+ .append(NEW_LINE + NEW_LINE)
+
+ stat.min?.let {
+ documentBuilder.append("**").append(context!!.getString(R.string.min)).append("** ")
+ .append(it.formatWhole())
+ .append(NEW_LINE + NEW_LINE)
+ }
+
+ stat.max?.let {
+ documentBuilder.append("**").append(context!!.getString(R.string.max)).append("** ")
+ .append(it.formatWhole())
+ .append(NEW_LINE + NEW_LINE)
+ }
+
+ stat.average?.let {
+ documentBuilder.append("**").append(context!!.getString(R.string.ave)).append("** ")
+ .append(it.formatWhole())
+ .append(NEW_LINE + NEW_LINE)
+ }
+
+ documentBuilder.append("").append(NEW_LINE + NEW_LINE)
+ }
}
}
- val seconds = (endDate - startDate) / 1000
- val days = seconds.toDouble() * 0.0000115741
+ fun generateTempStats()
+ {
+ documentBuilder.append("## ").append(context!!.getString(R.string.temperature_title))
+ .append(NEW_LINE + NEW_LINE)
- documentBuilder.append("## Stats").append(NEW_LINE + NEW_LINE)
- documentBuilder.append(" - **Total grow time:** ").append(String.format("%1$,.2f days", days)).append(NEW_LINE)
- documentBuilder.append(" - **Total waters:** ").append(totalWater.toString()).append(NEW_LINE)
- documentBuilder.append(" - **Total water used:** ").append(Unit.ML.to(selectedDelivery, totalWaterUsed).formatWhole()).append(selectedDelivery!!.label).append(NEW_LINE)
- documentBuilder.append(" - **Total flushes:** ").append(totalFlush.toString()).append(NEW_LINE)
- documentBuilder.append(" - **Average time between watering:** ").append(String.format("%1$,.2f days", TimeHelper.toDays(waterDifference) / totalWater.toDouble())).append(NEW_LINE + NEW_LINE)
+ viewModel.tempStats.min?.let {
+ documentBuilder.append("**").append(context!!.getString(R.string.min)).append("** ")
+ .append("${it.formatWhole()}°${viewModel.selectedTempUnit.label}")
+ .append(NEW_LINE + NEW_LINE)
+ }
- if (additives.isNotEmpty())
- {
- documentBuilder.append("### Nutrients used").append(NEW_LINE + NEW_LINE)
- additives.forEach { (k, v) ->
- documentBuilder.append(" - ").append(k)
- .append(" (total: ").append(Unit.ML.to(selectedMeasurement!!, v).formatWhole()).append(selectedMeasurement!!.label).append(")")
- .append(NEW_LINE)
+ viewModel.tempStats.max?.let {
+ documentBuilder.append("**").append(context!!.getString(R.string.max)).append("** ")
+ .append("${it.formatWhole()}°${viewModel.selectedTempUnit.label}")
+ .append(NEW_LINE + NEW_LINE)
+ }
+
+ viewModel.tempStats.average?.let {
+ documentBuilder.append("**").append(context!!.getString(R.string.ave)).append("** ")
+ .append("${it.formatWhole()}°${viewModel.selectedTempUnit.label}")
+ .append(NEW_LINE + NEW_LINE)
}
- documentBuilder.append(NEW_LINE)
- }
- documentBuilder.append("").append(NEW_LINE + NEW_LINE)
-
- val avePh = arrayOfNulls(3)
- StatsHelper.setInputData(plant, null, null, avePh)
- documentBuilder.append("### Input/runoff pH").append(NEW_LINE + NEW_LINE)
- documentBuilder.append(" - **Minimum input pH:** ").append(avePh[0]).append(NEW_LINE)
- documentBuilder.append(" - **Maximum input pH:** ").append(avePh[1]).append(NEW_LINE)
- documentBuilder.append(" - **Average input pH:** ").append(avePh[2]).append(NEW_LINE + NEW_LINE)
- documentBuilder.append("").append(NEW_LINE + NEW_LINE)
-
- tdsNames.forEach { tds ->
- documentBuilder.append("### ${tds.enStr}").append(NEW_LINE + NEW_LINE)
-
- val tdsArr = arrayOfNulls(3)
- StatsHelper.setTdsData(plant, null, null, tdsArr, tds)
- documentBuilder.append(" - **Minimum input ${tds.enStr}**: ").append(tdsArr[0]).append(tds.label).append(NEW_LINE)
- documentBuilder.append(" - **Maximum input ${tds.enStr}**: ").append(tdsArr[1]).append(tds.label).append(NEW_LINE)
- documentBuilder.append(" - **Average input ${tds.enStr}**: ").append(tdsArr[2]).append(tds.label).append(NEW_LINE + NEW_LINE)
- documentBuilder.append("").append(NEW_LINE + NEW_LINE)
+ documentBuilder.append("").append(NEW_LINE + NEW_LINE)
}
- val aveTemp = arrayOfNulls(3)
- StatsHelper.setTempData(plant, null, selectedTemp, null, aveTemp)
- documentBuilder.append("### Temperature (°${selectedTemp!!.label})").append(NEW_LINE + NEW_LINE)
- documentBuilder.append(" - **Minimum input temperature**: ").append(aveTemp[0]).append("°${selectedTemp!!.label}").append(NEW_LINE)
- documentBuilder.append(" - **Maximum input temperature**: ").append(aveTemp[1]).append("°${selectedTemp!!.label}").append(NEW_LINE)
- documentBuilder.append(" - **Average input temperature**: ").append(aveTemp[2]).append("°${selectedTemp!!.label}").append(NEW_LINE + NEW_LINE)
- documentBuilder.append("").append(NEW_LINE + NEW_LINE)
+ generateGeneralsStats()
+ generateAdditiveStats()
+ generatePhStats()
+ generateTdsStats()
+ generateTempStats()
}
override fun printPlantActions(plant: Plant)
@@ -311,9 +408,10 @@ class MarkdownProcessor : ExportProcessor()
if (action.additives.isNotEmpty())
{
documentBuilder.append(" / ")
- action.additives.forEach { additive ->
+ val additives = action.additives.sortedBy { it.description ?: "" }
+ additives.forEach { additive ->
documentBuilder.append("**${additive.description ?: ""}:** ").append(Unit.ML.to(selectedMeasurement, additive.amount ?: 0.0).formatWhole()).append(selectedMeasurement!!.label).append("/").append(selectedDelivery!!.label)
- if (action.additives.last() != additive) documentBuilder.append(" – ")
+ if (additives.last() != additive) documentBuilder.append(" – ")
}
}
}
@@ -337,8 +435,19 @@ class MarkdownProcessor : ExportProcessor()
documentBuilder.append(NEW_LINE)
}
- override fun printPlantImages(arrayList: ArrayList)
+ override fun printPlantImages(map: SortedMap>)
{
+ documentBuilder.append("## Images")
+ documentBuilder.append(NEW_LINE + NEW_LINE)
+
+ map.forEach { (k, items) ->
+ documentBuilder.append("### $k")
+ documentBuilder.append(NEW_LINE + NEW_LINE)
+ items.forEach { item ->
+ documentBuilder.append("")
+ documentBuilder.append(NEW_LINE + NEW_LINE)
+ }
+ }
}
override fun printGardenDetails(garden: Garden)
@@ -355,17 +464,17 @@ class MarkdownProcessor : ExportProcessor()
val aveTemp = arrayOfNulls(3)
StatsHelper.setTempData(garden, null, selectedTemp, null, aveTemp)
documentBuilder.append("### Temperature (°${selectedTemp!!.label})").append(NEW_LINE + NEW_LINE)
- documentBuilder.append(" - **Minimum temperature**: ").append(aveTemp[0]).append("°${selectedTemp!!.label}").append(NEW_LINE)
- documentBuilder.append(" - **Maximum temperature**: ").append(aveTemp[1]).append("°${selectedTemp!!.label}").append(NEW_LINE)
- documentBuilder.append(" - **Average temperature**: ").append(aveTemp[2]).append("°${selectedTemp!!.label}").append(NEW_LINE + NEW_LINE)
+ documentBuilder.append(" - **Minimum temperature:** ").append(aveTemp[0]).append("°${selectedTemp!!.label}").append(NEW_LINE)
+ documentBuilder.append(" - **Maximum temperature:** ").append(aveTemp[1]).append("°${selectedTemp!!.label}").append(NEW_LINE)
+ documentBuilder.append(" - **Average temperature:** ").append(aveTemp[2]).append("°${selectedTemp!!.label}").append(NEW_LINE + NEW_LINE)
documentBuilder.append("").append(NEW_LINE + NEW_LINE)
val aveHumidity = arrayOfNulls(3)
StatsHelper.setHumidityData(garden, null, null, aveHumidity)
documentBuilder.append("### Humidity").append(NEW_LINE + NEW_LINE)
- documentBuilder.append(" - **Minimum humidity**: ").append(aveHumidity[0]).append("%").append(NEW_LINE)
- documentBuilder.append(" - **Maximum humidity**: ").append(aveHumidity[1]).append("%").append(NEW_LINE)
- documentBuilder.append(" - **Average humidity**: ").append(aveHumidity[2]).append("%").append(NEW_LINE + NEW_LINE)
+ documentBuilder.append(" - **Minimum humidity:** ").append(aveHumidity[0]).append("%").append(NEW_LINE)
+ documentBuilder.append(" - **Maximum humidity:** ").append(aveHumidity[1]).append("%").append(NEW_LINE)
+ documentBuilder.append(" - **Average humidity:** ").append(aveHumidity[2]).append("%").append(NEW_LINE + NEW_LINE)
documentBuilder.append("").append(NEW_LINE + NEW_LINE)
}
@@ -397,7 +506,7 @@ class MarkdownProcessor : ExportProcessor()
}
is LightingChange -> {
- documentBuilder.append("**Lights on:** ").append(action.on).append(" **Lights off:**: ").append(action.off)
+ documentBuilder.append("**Lights on:** ").append(action.on).append(" **Lights off::** ").append(action.off)
}
else -> documentBuilder.append(" ")
diff --git a/app/src/main/java/me/anon/lib/ext/ClassUtils.kt b/app/src/main/java/me/anon/lib/ext/ClassUtils.kt
new file mode 100644
index 00000000..5ed10b23
--- /dev/null
+++ b/app/src/main/java/me/anon/lib/ext/ClassUtils.kt
@@ -0,0 +1,6 @@
+package me.anon.lib.ext
+
+/**
+ * Performs a transformation of an object type into another object type
+ */
+public inline fun T.transform(transformer: T.() -> O): O = transformer(this)
diff --git a/app/src/main/java/me/anon/lib/ext/EntryExt.kt b/app/src/main/java/me/anon/lib/ext/EntryExt.kt
new file mode 100644
index 00000000..90493717
--- /dev/null
+++ b/app/src/main/java/me/anon/lib/ext/EntryExt.kt
@@ -0,0 +1,68 @@
+package me.anon.lib.ext
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import androidx.core.graphics.scale
+import com.github.mikephil.charting.charts.Chart
+import com.github.mikephil.charting.charts.LineChart
+import com.github.mikephil.charting.components.Legend
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.data.Entry
+import me.anon.grow.R
+
+public fun Chart<*>.scaledBitmap(): Bitmap
+{
+ val returnedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
+ val canvas = Canvas(returnedBitmap)
+ var bgDrawable = background
+ if (bgDrawable != null) bgDrawable.draw(canvas)
+ else canvas.drawColor(Color.WHITE)
+
+ draw(canvas)
+
+ return returnedBitmap.scale((width.toFloat() / 1.5f).toInt(), (height.toFloat() / 1.5f).toInt()).also {
+ returnedBitmap.recycle()
+ }
+}
+
+public fun List.rollingAverage(): List
+{
+ val averageEntries = mutableListOf()
+
+ foldIndexed(0f) { index, acc, entry ->
+ val ret = entry.y + acc
+ averageEntries += Entry(this[index].x, ret / (index + 1))
+ ret
+ }
+
+ return averageEntries
+}
+
+public fun LineChart.style()
+{
+ setDrawGridBackground(false)
+ description = null
+ isScaleYEnabled = false
+ setDrawBorders(false)
+
+ axisLeft.labelCount = 8
+ axisLeft.setDrawTopYLabelEntry(true)
+ axisLeft.setDrawZeroLine(true)
+ axisLeft.setDrawGridLines(false)
+ axisLeft.textColor = R.attr.colorOnSurface.resolveColor(context!!)
+
+ axisRight.setDrawLabels(false)
+ axisRight.setDrawGridLines(false)
+ axisRight.setDrawAxisLine(false)
+
+ xAxis.setDrawGridLines(false)
+ xAxis.granularity = 1f
+ xAxis.position = XAxis.XAxisPosition.BOTTOM
+ xAxis.yOffset = 10f
+ xAxis.textColor = R.attr.colorOnSurface.resolveColor(context!!)
+
+ legend.form = Legend.LegendForm.CIRCLE
+ legend.textColor = R.attr.colorOnSurface.resolveColor(context!!)
+ legend.isWordWrapEnabled = true
+}
diff --git a/app/src/main/java/me/anon/lib/ext/IntUtils.kt b/app/src/main/java/me/anon/lib/ext/IntUtils.kt
index 66099eac..63a4edc7 100644
--- a/app/src/main/java/me/anon/lib/ext/IntUtils.kt
+++ b/app/src/main/java/me/anon/lib/ext/IntUtils.kt
@@ -3,10 +3,7 @@ package me.anon.lib.ext
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.TypedValue
-import androidx.annotation.AttrRes
-import androidx.annotation.ColorInt
-import androidx.annotation.ColorRes
-import androidx.annotation.DrawableRes
+import androidx.annotation.*
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
@@ -62,6 +59,11 @@ public fun @receiver:ColorRes Int.getColor(themedContext: Context): Int
return ContextCompat.getColor(themedContext, this)
}
+/**
+ * Resolves a colour int from an attr res. Will only work for attr type color/reference (to color res)
+ */
+public fun @receiver:AttrRes Int.resolveDimen(themedContext: Context?): Float = themedContext?.resources?.getDimension(this) ?: 0f
+
/**
* Resolves a drawable object from a drawable res
*/
diff --git a/app/src/main/java/me/anon/lib/ext/NumberUtils.kt b/app/src/main/java/me/anon/lib/ext/NumberUtils.kt
index 802129a8..e52313c9 100644
--- a/app/src/main/java/me/anon/lib/ext/NumberUtils.kt
+++ b/app/src/main/java/me/anon/lib/ext/NumberUtils.kt
@@ -1,5 +1,7 @@
package me.anon.lib.ext
+import android.content.Context
+import android.util.TypedValue
import kotlin.math.round
public fun Double.round(decimals: Int): Double
@@ -27,3 +29,30 @@ public fun Number?.formatWhole(): String
}
} ?: "0"
}
+
+public fun Long.toDays(): Double = (this / (1000.0 * 60.0 * 60.0 * 24.0))
+
+public fun Number.dip(context: Context): Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), context.resources.displayMetrics)
+public fun Number.sp(context: Context): Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this.toFloat(), context.resources.displayMetrics)
+public fun Number.mm(context: Context): Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, this.toFloat(), context.resources.displayMetrics)
+public fun Number.pt(context: Context): Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, this.toFloat(), context.resources.displayMetrics)
+
+public fun Double?.max(other: Double?): Double?
+{
+ return when
+ {
+ other == null -> this
+ this == null -> other
+ else -> kotlin.math.max(this, other)
+ }
+}
+
+public fun Double?.min(other: Double?): Double?
+{
+ return when
+ {
+ other == null -> this
+ this == null -> other
+ else -> kotlin.math.min(this, other)
+ }
+}
diff --git a/app/src/main/java/me/anon/lib/ext/StringExt.kt b/app/src/main/java/me/anon/lib/ext/StringExt.kt
new file mode 100644
index 00000000..c2929fc4
--- /dev/null
+++ b/app/src/main/java/me/anon/lib/ext/StringExt.kt
@@ -0,0 +1,6 @@
+package me.anon.lib.ext
+
+public fun String.normalise(): String
+{
+ return toLowerCase().replace("[^a-zA-Z0-9+]".toRegex(), "_")
+}
diff --git a/app/src/main/java/me/anon/lib/helper/BackupHelper.kt b/app/src/main/java/me/anon/lib/helper/BackupHelper.kt
index 993a9c7a..d464ccb8 100644
--- a/app/src/main/java/me/anon/lib/helper/BackupHelper.kt
+++ b/app/src/main/java/me/anon/lib/helper/BackupHelper.kt
@@ -82,17 +82,20 @@ object BackupHelper
public fun limitBackups(size: String = MainApplication.getDefaultPreferences().getString("backup_size", "20")!!)
{
File(FILES_PATH).listFiles()?.let {
- val sorted = ArrayList(it.sortedBy { it.lastModified() })
- val limit = size.toSafeInt() * 1_048_576
-
- var currentSize = backupSize()
- while (currentSize > limit)
+ if (it.isNotEmpty())
{
- val remove = sorted.removeAt(0)
- val len = remove.length()
- if (remove.delete())
+ val sorted = ArrayList(it.sortedBy { it.lastModified() })
+ val limit = size.toSafeInt() * 1_048_576
+
+ var currentSize = backupSize()
+ while (currentSize > limit)
{
- currentSize -= len
+ val remove = sorted.removeAt(0)
+ val len = remove.length()
+ if (remove.delete())
+ {
+ currentSize -= len
+ }
}
}
}
diff --git a/app/src/main/java/me/anon/lib/helper/FabAnimator.java b/app/src/main/java/me/anon/lib/helper/FabAnimator.java
deleted file mode 100644
index 9e3cd5b9..00000000
--- a/app/src/main/java/me/anon/lib/helper/FabAnimator.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package me.anon.lib.helper;
-
-import android.view.View;
-
-/**
- * // TODO: Add class description
- *
- * @author 7LPdWcaW
- * @documentation // TODO Reference flow doc
- * @project GrowTracker
- */
-public class FabAnimator
-{
- public static void animateUp(View fab)
- {
- if (fab == null) return;
-
- fab.animate().translationYBy(-(fab.getHeight() * 0.85f)).setDuration(200).start();
- }
-
- public static void animateDown(View fab)
- {
- if (fab == null) return;
-
- fab.animate().translationYBy((fab.getHeight() * 0.85f)).setDuration(200).start();
- }
-}
diff --git a/app/src/main/java/me/anon/lib/helper/StatsHelper.java b/app/src/main/java/me/anon/lib/helper/StatsHelper.java
index 8ac06f37..776efbc0 100644
--- a/app/src/main/java/me/anon/lib/helper/StatsHelper.java
+++ b/app/src/main/java/me/anon/lib/helper/StatsHelper.java
@@ -2,20 +2,15 @@
import android.content.Context;
import android.graphics.Color;
-import android.widget.TextView;
+import android.view.ContextThemeWrapper;
import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.charts.LineChart;
-import com.github.mikephil.charting.components.MarkerView;
-import com.github.mikephil.charting.components.XAxis;
-import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.formatter.IndexAxisValueFormatter;
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.ColorTemplate;
import com.github.mikephil.charting.utils.ViewPortHandler;
import org.threeten.bp.DateTimeUtils;
@@ -25,32 +20,22 @@
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Random;
import java.util.Set;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.appcompat.view.ContextThemeWrapper;
import androidx.core.graphics.ColorUtils;
-import androidx.core.util.Pair;
import me.anon.grow.R;
import me.anon.lib.TdsUnit;
import me.anon.lib.TempUnit;
-import me.anon.lib.Unit;
+import me.anon.lib.ext.EntryExtKt;
import me.anon.lib.ext.IntUtilsKt;
import me.anon.lib.ext.NumberUtilsKt;
import me.anon.model.Action;
-import me.anon.model.Additive;
import me.anon.model.Garden;
import me.anon.model.HumidityChange;
import me.anon.model.Plant;
-import me.anon.model.PlantStage;
import me.anon.model.TemperatureChange;
-import me.anon.model.Water;
/**
* Helper class used for generating statistics for plant
@@ -67,59 +52,59 @@ public class StatsHelper
public static void styleGraph(BarLineChartBase chart)
{
- Context context = new ContextThemeWrapper(chart.getContext(), R.style.AppTheme);
- chart.setDrawGridBackground(false);
- chart.setGridBackgroundColor(0x00ffffff);
- chart.getAxisLeft().setDrawGridLines(false);
- chart.getXAxis().setDrawGridLines(false);
- chart.getLegend().setTextColor(IntUtilsKt.resolveColor(R.attr.colorAccent, context));
- chart.getLegend().setTextSize(12f);
- chart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM);
- chart.getXAxis().setTextColor(IntUtilsKt.resolveColor(R.attr.colorAccent, context));
- chart.getXAxis().setTextSize(12f);
- chart.getAxisRight().setEnabled(false);
- chart.getAxisLeft().setTextColor(IntUtilsKt.resolveColor(R.attr.colorAccent, context));
- chart.getAxisLeft().setTextSize(12f);
- chart.getAxisLeft().setValueFormatter(new YAxisValueFormatter()
- {
- @Override public String getFormattedValue(float value, YAxis yAxis)
- {
- return NumberUtilsKt.formatWhole(value);
- }
- });
- chart.getAxisLeft().setStartAtZero(false);
- chart.getAxisRight().setValueFormatter(new YAxisValueFormatter()
- {
- @Override public String getFormattedValue(float value, YAxis yAxis)
- {
- return NumberUtilsKt.formatWhole(value);
- }
- });
- chart.setScaleYEnabled(false);
- chart.setDescription("");
- chart.getAxisLeft().setXOffset(8.0f);
- chart.getLegend().setWordWrapEnabled(true);
- chart.setTouchEnabled(true);
- chart.setHighlightPerTapEnabled(true);
- chart.setNoDataText(context.getString(R.string.no_data));
- chart.setMarkerView(new MarkerView(context, R.layout.chart_marker)
- {
- @Override
- public void refreshContent(Entry e, Highlight highlight)
- {
- ((TextView)findViewById(R.id.content)).setText("" + e.getVal());
- }
-
- @Override public int getXOffset(float xpos)
- {
- return -(getWidth() / 2);
- }
-
- @Override public int getYOffset(float ypos)
- {
- return -getHeight();
- }
- });
+// Context context = new ContextThemeWrapper(chart.getContext(), R.style.AppTheme);
+// chart.setDrawGridBackground(false);
+// chart.setGridBackgroundColor(0x00ffffff);
+// chart.getAxisLeft().setDrawGridLines(false);
+// chart.getXAxis().setDrawGridLines(false);
+// chart.getLegend().setTextColor(IntUtilsKt.resolveColor(R.attr.colorAccent, context));
+// chart.getLegend().setTextSize(12f);
+// chart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM);
+// chart.getXAxis().setTextColor(IntUtilsKt.resolveColor(R.attr.colorAccent, context));
+// chart.getXAxis().setTextSize(12f);
+// chart.getAxisRight().setEnabled(false);
+// chart.getAxisLeft().setTextColor(IntUtilsKt.resolveColor(R.attr.colorAccent, context));
+// chart.getAxisLeft().setTextSize(12f);
+// chart.getAxisLeft().setValueFormatter(new YAxisValueFormatter()
+// {
+// @Override public String getFormattedValue(float value, YAxis yAxis)
+// {
+// return NumberUtilsKt.formatWhole(value);
+// }
+// });
+// chart.getAxisLeft().setStartAtZero(false);
+// chart.getAxisRight().setValueFormatter(new YAxisValueFormatter()
+// {
+// @Override public String getFormattedValue(float value, YAxis yAxis)
+// {
+// return NumberUtilsKt.formatWhole(value);
+// }
+// });
+// chart.setScaleYEnabled(false);
+// chart.setDescription("");
+// chart.getAxisLeft().setXOffset(8.0f);
+// chart.getLegend().setWordWrapEnabled(true);
+// chart.setTouchEnabled(true);
+// chart.setHighlightPerTapEnabled(true);
+// chart.setNoDataText(context.getString(R.string.no_data));
+// chart.setMarkerView(new MarkerView(context, R.layout.chart_marker)
+// {
+// @Override
+// public void refreshContent(Entry e, Highlight highlight)
+// {
+// ((TextView)findViewById(R.id.content)).setText("" + e.getVal());
+// }
+//
+// @Override public int getXOffset(float xpos)
+// {
+// return -(getWidth() / 2);
+// }
+//
+// @Override public int getYOffset(float ypos)
+// {
+// return -getHeight();
+// }
+// });
}
public static void styleDataset(Context context, LineDataSet data, int colour)
@@ -127,7 +112,6 @@ public static void styleDataset(Context context, LineDataSet data, int colour)
context = new ContextThemeWrapper(context, R.style.AppTheme);
data.setValueTextColor(IntUtilsKt.resolveColor(R.attr.colorAccent, context));
data.setCircleColor(IntUtilsKt.resolveColor(R.attr.colorAccent, context));
- data.setDrawCubic(true);
data.setCubicIntensity(0.05f);
data.setLineWidth(2.0f);
data.setDrawCircleHole(true);
@@ -139,105 +123,105 @@ public static void styleDataset(Context context, LineDataSet data, int colour)
data.setHighlightLineWidth(2f);
data.setHighLightColor(ColorUtils.setAlphaComponent(colour, 96));
data.setDrawValues(false);
- data.setValueFormatter(formatter);
+// data.setValueFormatter(formatter);
}
public static void setAdditiveData(Plant plant, @NonNull Context context, @Nullable LineChart chart, @NonNull Set checkedAdditives)
{
- final Unit measurement = Unit.getSelectedMeasurementUnit(context);
- final Unit delivery = Unit.getSelectedDeliveryUnit(context);
-
- ArrayList actions = plant.getActions();
- ArrayList>> vals = new ArrayList<>();
- ArrayList xVals = new ArrayList<>();
- final Set additiveNames = new HashSet<>();
- LineData data = new LineData();
- LinkedHashMap stageTimes = plant.getStages();
- double min = Double.MAX_VALUE;
- double max = Double.MIN_VALUE;
-
- for (Action action : actions)
- {
- if (action instanceof Water)
- {
- List actionAdditives = ((Water)action).getAdditives();
- for (Additive additive : actionAdditives)
- {
- double amount = Unit.ML.to(measurement, additive.getAmount());
- additiveNames.add(additive.getDescription());
- min = Math.min(min, amount);
- max = Math.max(max, amount);
- }
-
- PlantStage stage = null;
- long changeDate = 0;
- ListIterator iterator = new ArrayList(stageTimes.keySet()).listIterator(stageTimes.size());
- while (iterator.hasPrevious())
- {
- PlantStage key = iterator.previous();
- Action changeAction = stageTimes.get(key);
- if (action.getDate() > changeAction.getDate())
- {
- stage = key;
- changeDate = changeAction.getDate();
- }
- }
-
- long difference = action.getDate() - changeDate;
- if (stage != null)
- {
- xVals.add(((int)TimeHelper.toDays(difference) + "" + context.getString(stage.getPrintString()).charAt(0)).toLowerCase());
- }
- else
- {
- xVals.add("");
- }
- }
- }
-
- ArrayList namesList = new ArrayList<>(additiveNames);
- ArrayList dataSets = new ArrayList<>();
- final String[] colours = chart.getResources().getStringArray(R.array.stats_colours);
- for (String additiveName : checkedAdditives)
- {
- int index = 0;
- int color = dataSets.size() < colours.length ? Color.parseColor(colours[namesList.indexOf(additiveName)]) : (additiveName.hashCode() + new Random().nextInt(0xffffff));
- ArrayList additiveValues = new ArrayList<>();
- for (Action action : actions)
- {
- if (action instanceof Water)
- {
- for (Additive additive : ((Water)action).getAdditives())
- {
- if (additiveName.equals(additive.getDescription()))
- {
- double amount = Unit.ML.to(measurement, additive.getAmount());
-
- Entry entry = new Entry((float)amount, index);
- entry.setData(color);
- additiveValues.add(entry);
- }
- }
-
- index++;
- }
- }
-
- LineDataSet dataSet = new LineDataSet(additiveValues, additiveName);
-
- StatsHelper.styleDataset(context, dataSet, color);
-
- dataSet.setValueFormatter(StatsHelper.formatter);
- dataSets.add(dataSet);
- }
-
- LineData lineData = new LineData(xVals, dataSets);
- lineData.setValueFormatter(StatsHelper.formatter);
- lineData.setValueTextColor(IntUtilsKt.resolveColor(android.R.attr.textColorSecondary, context));
-
- styleGraph(chart);
-
- chart.setData(lineData);
+// final Unit measurement = Unit.getSelectedMeasurementUnit(context);
+// final Unit delivery = Unit.getSelectedDeliveryUnit(context);
+//
+// ArrayList actions = plant.getActions();
+// ArrayList>> vals = new ArrayList<>();
+// ArrayList xVals = new ArrayList<>();
+// final Set additiveNames = new HashSet<>();
+// LineData data = new LineData();
+// LinkedHashMap stageTimes = plant.getStages();
+// double min = Double.MAX_VALUE;
+// double max = Double.MIN_VALUE;
+//
+// for (Action action : actions)
+// {
+// if (action instanceof Water)
+// {
+// List actionAdditives = ((Water)action).getAdditives();
+// for (Additive additive : actionAdditives)
+// {
+// double amount = Unit.ML.to(measurement, additive.getAmount());
+// additiveNames.add(additive.getDescription());
+// min = Math.min(min, amount);
+// max = Math.max(max, amount);
+// }
+//
+// PlantStage stage = null;
+// long changeDate = 0;
+// ListIterator iterator = new ArrayList(stageTimes.keySet()).listIterator(stageTimes.size());
+// while (iterator.hasPrevious())
+// {
+// PlantStage key = iterator.previous();
+// Action changeAction = stageTimes.get(key);
+// if (action.getDate() > changeAction.getDate())
+// {
+// stage = key;
+// changeDate = changeAction.getDate();
+// }
+// }
+//
+// long difference = action.getDate() - changeDate;
+// if (stage != null)
+// {
+// xVals.add(((int)TimeHelper.toDays(difference) + "" + context.getString(stage.getPrintString()).charAt(0)).toLowerCase());
+// }
+// else
+// {
+// xVals.add("");
+// }
+// }
+// }
+//
+// ArrayList namesList = new ArrayList<>(additiveNames);
+// ArrayList dataSets = new ArrayList<>();
+// final String[] colours = chart.getResources().getStringArray(R.array.stats_colours);
+// for (String additiveName : checkedAdditives)
+// {
+// int index = 0;
+// int color = dataSets.size() < colours.length ? Color.parseColor(colours[namesList.indexOf(additiveName)]) : (additiveName.hashCode() + new Random().nextInt(0xffffff));
+// ArrayList additiveValues = new ArrayList<>();
+// for (Action action : actions)
+// {
+// if (action instanceof Water)
+// {
+// for (Additive additive : ((Water)action).getAdditives())
+// {
+// if (additiveName.equals(additive.getDescription()))
+// {
+// double amount = Unit.ML.to(measurement, additive.getAmount());
+//
+// Entry entry = new Entry((float)amount, index);
+// entry.setData(color);
+// additiveValues.add(entry);
+// }
+// }
+//
+// index++;
+// }
+// }
+//
+// LineDataSet dataSet = new LineDataSet(additiveValues, additiveName);
+//
+// StatsHelper.styleDataset(context, dataSet, color);
+//
+// dataSet.setValueFormatter(StatsHelper.formatter);
+// dataSets.add(dataSet);
+// }
+//
+// LineData lineData = new LineData(xVals, dataSets);
+// lineData.setValueFormatter(StatsHelper.formatter);
+// lineData.setValueTextColor(IntUtilsKt.resolveColor(android.R.attr.textColorSecondary, context));
+//
+// styleGraph(chart);
+//
+// chart.setData(lineData);
}
/**
@@ -249,97 +233,97 @@ public static void setAdditiveData(Plant plant, @NonNull Context context, @Nulla
*/
public static void setInputData(Plant plant, @Nullable Context context, @Nullable LineChart chart, String[] additionalRef)
{
- ArrayList inputVals = new ArrayList<>();
- ArrayList runoffVals = new ArrayList<>();
- ArrayList averageVals = new ArrayList<>();
- ArrayList xVals = new ArrayList<>();
- LineData data = new LineData();
- LinkedHashMap stageTimes = plant.getStages();
- float min = Float.MAX_VALUE;
- float max = Float.MIN_VALUE;
- float totalIn = 0;
-
- int index = 0;
- for (Action action : plant.getActions())
- {
- if (action instanceof Water)
- {
- if (((Water)action).getPh() != null)
- {
- inputVals.add(new Entry(((Water)action).getPh().floatValue(), index));
- min = Math.min(min, ((Water)action).getPh().floatValue());
- max = Math.max(max, ((Water)action).getPh().floatValue());
-
- totalIn += ((Water)action).getPh().floatValue();
- }
-
- if (((Water)action).getRunoff() != null)
- {
- runoffVals.add(new Entry(((Water)action).getRunoff().floatValue(), index));
- min = Math.min(min, ((Water)action).getRunoff().floatValue());
- max = Math.max(max, ((Water)action).getRunoff().floatValue());
- }
-
- PlantStage stage = null;
- long changeDate = 0;
- ListIterator iterator = new ArrayList(stageTimes.keySet()).listIterator(stageTimes.size());
- while (iterator.hasPrevious())
- {
- PlantStage key = iterator.previous();
- Action changeAction = stageTimes.get(key);
- if (action.getDate() > changeAction.getDate())
- {
- stage = key;
- changeDate = changeAction.getDate();
- }
- }
-
- long difference = action.getDate() - changeDate;
- if (stage != null)
- {
- xVals.add(((int)TimeHelper.toDays(difference) + "" + (context != null ? context.getString(stage.getPrintString()) : stage.getEnString()).charAt(0)).toLowerCase());
- }
- else
- {
- xVals.add("");
- }
-
- index++;
- }
- }
-
- if (chart != null && context != null)
- {
- LineDataSet dataSet = new LineDataSet(inputVals, context.getString(R.string.stat_input_ph));
- styleDataset(context, dataSet, ColorTemplate.COLORFUL_COLORS[0]);
-
- LineDataSet runoffDataSet = new LineDataSet(runoffVals, context.getString(R.string.stat_runoff_ph));
- styleDataset(context, dataSet, ColorTemplate.COLORFUL_COLORS[1]);
-
- LineDataSet averageDataSet = new LineDataSet(averageVals, context.getString(R.string.stat_average_ph));
- styleDataset(context, dataSet, ColorTemplate.COLORFUL_COLORS[2]);
- averageDataSet.setValueFormatter(null);
-
- ArrayList dataSets = new ArrayList();
- dataSets.add(dataSet);
- dataSets.add(runoffDataSet);
-
- LineData lineData = new LineData(xVals, dataSets);
- lineData.setValueFormatter(formatter);
-
- styleGraph(chart);
- chart.getAxisLeft().setAxisMinValue(min - 0.5f);
- chart.getAxisLeft().setAxisMaxValue(max + 0.5f);
-
- chart.setData(lineData);
- }
-
- if (additionalRef != null)
- {
- additionalRef[0] = min == Float.MAX_VALUE ? "-" : NumberUtilsKt.formatWhole(min);
- additionalRef[1] = max == Float.MIN_VALUE ? "-" : NumberUtilsKt.formatWhole(max);
- additionalRef[2] = NumberUtilsKt.formatWhole(totalIn / (double)inputVals.size());
- }
+// ArrayList inputVals = new ArrayList<>();
+// ArrayList runoffVals = new ArrayList<>();
+// ArrayList averageVals = new ArrayList<>();
+// ArrayList xVals = new ArrayList<>();
+// LineData data = new LineData();
+// LinkedHashMap stageTimes = plant.getStages();
+// float min = Float.MAX_VALUE;
+// float max = Float.MIN_VALUE;
+// float totalIn = 0;
+//
+// int index = 0;
+// for (Action action : plant.getActions())
+// {
+// if (action instanceof Water)
+// {
+// if (((Water)action).getPh() != null)
+// {
+// inputVals.add(new Entry(((Water)action).getPh().floatValue(), index));
+// min = Math.min(min, ((Water)action).getPh().floatValue());
+// max = Math.max(max, ((Water)action).getPh().floatValue());
+//
+// totalIn += ((Water)action).getPh().floatValue();
+// }
+//
+// if (((Water)action).getRunoff() != null)
+// {
+// runoffVals.add(new Entry(((Water)action).getRunoff().floatValue(), index));
+// min = Math.min(min, ((Water)action).getRunoff().floatValue());
+// max = Math.max(max, ((Water)action).getRunoff().floatValue());
+// }
+//
+// PlantStage stage = null;
+// long changeDate = 0;
+// ListIterator iterator = new ArrayList(stageTimes.keySet()).listIterator(stageTimes.size());
+// while (iterator.hasPrevious())
+// {
+// PlantStage key = iterator.previous();
+// Action changeAction = stageTimes.get(key);
+// if (action.getDate() > changeAction.getDate())
+// {
+// stage = key;
+// changeDate = changeAction.getDate();
+// }
+// }
+//
+// long difference = action.getDate() - changeDate;
+// if (stage != null)
+// {
+// xVals.add(((int)TimeHelper.toDays(difference) + "" + (context != null ? context.getString(stage.getPrintString()) : stage.getEnString()).charAt(0)).toLowerCase());
+// }
+// else
+// {
+// xVals.add("");
+// }
+//
+// index++;
+// }
+// }
+//
+// if (chart != null && context != null)
+// {
+// LineDataSet dataSet = new LineDataSet(inputVals, context.getString(R.string.stat_input_ph));
+// styleDataset(context, dataSet, ColorTemplate.COLORFUL_COLORS[0]);
+//
+// LineDataSet runoffDataSet = new LineDataSet(runoffVals, context.getString(R.string.stat_runoff_ph));
+// styleDataset(context, dataSet, ColorTemplate.COLORFUL_COLORS[1]);
+//
+// LineDataSet averageDataSet = new LineDataSet(averageVals, context.getString(R.string.stat_average_ph));
+// styleDataset(context, dataSet, ColorTemplate.COLORFUL_COLORS[2]);
+// averageDataSet.setValueFormatter(null);
+//
+// ArrayList dataSets = new ArrayList();
+// dataSets.add(dataSet);
+// dataSets.add(runoffDataSet);
+//
+// LineData lineData = new LineData(xVals, dataSets);
+// lineData.setValueFormatter(formatter);
+//
+// styleGraph(chart);
+// chart.getAxisLeft().setAxisMinValue(min - 0.5f);
+// chart.getAxisLeft().setAxisMaxValue(max + 0.5f);
+//
+// chart.setData(lineData);
+// }
+//
+// if (additionalRef != null)
+// {
+// additionalRef[0] = min == Float.MAX_VALUE ? "-" : NumberUtilsKt.formatWhole(min);
+// additionalRef[1] = max == Float.MIN_VALUE ? "-" : NumberUtilsKt.formatWhole(max);
+// additionalRef[2] = NumberUtilsKt.formatWhole(totalIn / (double)inputVals.size());
+// }
}
/**
@@ -351,83 +335,83 @@ public static void setInputData(Plant plant, @Nullable Context context, @Nullabl
*/
public static void setTdsData(Plant plant, @Nullable Context context, @Nullable LineChart chart, String[] additionalRef, TdsUnit selectedUnit)
{
- ArrayList vals = new ArrayList<>();
- ArrayList xVals = new ArrayList<>();
- LineData data = new LineData();
- LinkedHashMap stageTimes = plant.getStages();
-
- float min = Float.MAX_VALUE;
- float max = Float.MIN_VALUE;
- float total = 0f;
-
- int index = 0;
- for (Action action : plant.getActions())
- {
- if (action instanceof Water && ((Water)action).getTds() != null && ((Water)action).getTds().getType() == selectedUnit)
- {
- float value = ((Water)action).getTds().getAmount().floatValue();
-
- vals.add(new Entry(value, index++));
- PlantStage stage = null;
- long changeDate = 0;
- ListIterator iterator = new ArrayList(stageTimes.keySet()).listIterator(stageTimes.size());
- while (iterator.hasPrevious())
- {
- PlantStage key = iterator.previous();
- Action changeAction = stageTimes.get(key);
- if (action.getDate() > changeAction.getDate())
- {
- stage = key;
- changeDate = changeAction.getDate();
- }
- }
-
- long difference = action.getDate() - changeDate;
- if (stage != null)
- {
- xVals.add(((int)TimeHelper.toDays(difference) + "" + (context != null ? context.getString(stage.getPrintString()) : stage.getEnString()).charAt(0)).toLowerCase());
- }
- else
- {
- xVals.add("");
- }
-
- min = Math.min(min, value);
- max = Math.max(max, value);
- total += value;
- }
- }
-
- if (chart != null && context != null)
- {
- LineDataSet dataSet = new LineDataSet(vals, selectedUnit.getLabel());
- styleDataset(context, dataSet, Color.parseColor(context.getResources().getStringArray(R.array.stats_colours)[0]));
- styleGraph(chart);
-
- chart.getAxisLeft().setValueFormatter(new YAxisValueFormatter()
- {
- @Override public String getFormattedValue(float value, YAxis yAxis)
- {
- return NumberUtilsKt.formatWhole(value);
- }
- });
- dataSet.setValueFormatter(new ValueFormatter()
- {
- @Override public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler)
- {
- return NumberUtilsKt.formatWhole(value);
- }
- });
-
- chart.setData(new LineData(xVals, dataSet));
- }
-
- if (additionalRef != null)
- {
- additionalRef[0] = min == Float.MAX_VALUE ? "-" : NumberUtilsKt.formatWhole(min);
- additionalRef[1] = max == Float.MIN_VALUE ? "-" : NumberUtilsKt.formatWhole(max);
- additionalRef[2] = NumberUtilsKt.formatWhole(total / (double)vals.size());
- }
+// ArrayList vals = new ArrayList<>();
+// ArrayList xVals = new ArrayList<>();
+// LineData data = new LineData();
+// LinkedHashMap stageTimes = plant.getStages();
+//
+// float min = Float.MAX_VALUE;
+// float max = Float.MIN_VALUE;
+// float total = 0f;
+//
+// int index = 0;
+// for (Action action : plant.getActions())
+// {
+// if (action instanceof Water && ((Water)action).getTds() != null && ((Water)action).getTds().getType() == selectedUnit)
+// {
+// float value = ((Water)action).getTds().getAmount().floatValue();
+//
+// vals.add(new Entry(value, index++));
+// PlantStage stage = null;
+// long changeDate = 0;
+// ListIterator iterator = new ArrayList(stageTimes.keySet()).listIterator(stageTimes.size());
+// while (iterator.hasPrevious())
+// {
+// PlantStage key = iterator.previous();
+// Action changeAction = stageTimes.get(key);
+// if (action.getDate() > changeAction.getDate())
+// {
+// stage = key;
+// changeDate = changeAction.getDate();
+// }
+// }
+//
+// long difference = action.getDate() - changeDate;
+// if (stage != null)
+// {
+// xVals.add(((int)TimeHelper.toDays(difference) + "" + (context != null ? context.getString(stage.getPrintString()) : stage.getEnString()).charAt(0)).toLowerCase());
+// }
+// else
+// {
+// xVals.add("");
+// }
+//
+// min = Math.min(min, value);
+// max = Math.max(max, value);
+// total += value;
+// }
+// }
+//
+// if (chart != null && context != null)
+// {
+// LineDataSet dataSet = new LineDataSet(vals, selectedUnit.getLabel());
+// styleDataset(context, dataSet, Color.parseColor(context.getResources().getStringArray(R.array.stats_colours)[0]));
+// styleGraph(chart);
+//
+// chart.getAxisLeft().setValueFormatter(new YAxisValueFormatter()
+// {
+// @Override public String getFormattedValue(float value, YAxis yAxis)
+// {
+// return NumberUtilsKt.formatWhole(value);
+// }
+// });
+// dataSet.setValueFormatter(new ValueFormatter()
+// {
+// @Override public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler)
+// {
+// return NumberUtilsKt.formatWhole(value);
+// }
+// });
+//
+// chart.setData(new LineData(xVals, dataSet));
+// }
+//
+// if (additionalRef != null)
+// {
+// additionalRef[0] = min == Float.MAX_VALUE ? "-" : NumberUtilsKt.formatWhole(min);
+// additionalRef[1] = max == Float.MIN_VALUE ? "-" : NumberUtilsKt.formatWhole(max);
+// additionalRef[2] = NumberUtilsKt.formatWhole(total / (double)vals.size());
+// }
}
/**
@@ -445,72 +429,72 @@ public static void setTempData(Plant plant, @NonNull Context context, @Nullable
public static void setTempData(Plant plant, @Nullable Context context, TempUnit tempUnit, @Nullable LineChart chart, String[] additionalRef)
{
- ArrayList vals = new ArrayList<>();
- ArrayList xVals = new ArrayList<>();
- LineData data = new LineData();
- LinkedHashMap stageTimes = plant.getStages();
- float min = Float.MAX_VALUE;
- float max = Float.MIN_VALUE;
- float total = 0;
-
- int index = 0;
- for (Action action : plant.getActions())
- {
- if (action instanceof Water && ((Water)action).getTemp() != null)
- {
- float temperature = (float)TempUnit.CELCIUS.to(tempUnit, ((Water)action).getTemp());
-
- vals.add(new Entry(temperature, index++));
- PlantStage stage = null;
- long changeDate = 0;
- ListIterator iterator = new ArrayList(stageTimes.keySet()).listIterator(stageTimes.size());
- while (iterator.hasPrevious())
- {
- PlantStage key = iterator.previous();
- Action changeAction = stageTimes.get(key);
- if (action.getDate() > changeAction.getDate())
- {
- stage = key;
- changeDate = changeAction.getDate();
- }
- }
-
- long difference = action.getDate() - changeDate;
- if (stage != null)
- {
- xVals.add(((int)TimeHelper.toDays(difference) + "" + (context != null ? context.getString(stage.getPrintString()) : stage.getEnString()).charAt(0)).toLowerCase());
- }
- else
- {
- xVals.add("");
- }
-
- min = Math.min(min, temperature);
- max = Math.max(max, temperature);
- total += temperature;
- }
- }
-
- if (chart != null)
- {
- LineDataSet dataSet = new LineDataSet(vals, context.getString(R.string.stat_temerature));
- styleDataset(context, dataSet, Color.parseColor(context.getResources().getStringArray(R.array.stats_colours)[0]));
-
- LineData lineData = new LineData(xVals, dataSet);
- lineData.setValueFormatter(formatter);
-
- chart.getAxisLeft().setAxisMinValue(min - 5f);
- chart.getAxisLeft().setAxisMaxValue(max + 5f);
- styleGraph(chart);
- chart.setData(lineData);
- }
-
- if (additionalRef != null)
- {
- additionalRef[0] = min == Float.MAX_VALUE ? "-" : NumberUtilsKt.formatWhole((int)min);
- additionalRef[1] = max == Float.MIN_VALUE ? "-" : NumberUtilsKt.formatWhole((int)max);
- additionalRef[2] = NumberUtilsKt.formatWhole((int)(total / (double)vals.size()));
- }
+// ArrayList vals = new ArrayList<>();
+// ArrayList xVals = new ArrayList<>();
+// LineData data = new LineData();
+// LinkedHashMap stageTimes = plant.getStages();
+// float min = Float.MAX_VALUE;
+// float max = Float.MIN_VALUE;
+// float total = 0;
+//
+// int index = 0;
+// for (Action action : plant.getActions())
+// {
+// if (action instanceof Water && ((Water)action).getTemp() != null)
+// {
+// float temperature = (float)TempUnit.CELCIUS.to(tempUnit, ((Water)action).getTemp());
+//
+// vals.add(new Entry(temperature, index++));
+// PlantStage stage = null;
+// long changeDate = 0;
+// ListIterator iterator = new ArrayList(stageTimes.keySet()).listIterator(stageTimes.size());
+// while (iterator.hasPrevious())
+// {
+// PlantStage key = iterator.previous();
+// Action changeAction = stageTimes.get(key);
+// if (action.getDate() > changeAction.getDate())
+// {
+// stage = key;
+// changeDate = changeAction.getDate();
+// }
+// }
+//
+// long difference = action.getDate() - changeDate;
+// if (stage != null)
+// {
+// xVals.add(((int)TimeHelper.toDays(difference) + "" + (context != null ? context.getString(stage.getPrintString()) : stage.getEnString()).charAt(0)).toLowerCase());
+// }
+// else
+// {
+// xVals.add("");
+// }
+//
+// min = Math.min(min, temperature);
+// max = Math.max(max, temperature);
+// total += temperature;
+// }
+// }
+//
+// if (chart != null)
+// {
+// LineDataSet dataSet = new LineDataSet(vals, context.getString(R.string.stat_temerature));
+// styleDataset(context, dataSet, Color.parseColor(context.getResources().getStringArray(R.array.stats_colours)[0]));
+//
+// LineData lineData = new LineData(xVals, dataSet);
+// lineData.setValueFormatter(formatter);
+//
+// chart.getAxisLeft().setAxisMinValue(min - 5f);
+// chart.getAxisLeft().setAxisMaxValue(max + 5f);
+// styleGraph(chart);
+// chart.setData(lineData);
+// }
+//
+// if (additionalRef != null)
+// {
+// additionalRef[0] = min == Float.MAX_VALUE ? "-" : NumberUtilsKt.formatWhole((int)min);
+// additionalRef[1] = max == Float.MIN_VALUE ? "-" : NumberUtilsKt.formatWhole((int)max);
+// additionalRef[2] = NumberUtilsKt.formatWhole((int)(total / (double)vals.size()));
+// }
}
/**
@@ -536,7 +520,7 @@ public static void setTempData(Garden garden, @NonNull Context context, @Nullabl
public static void setTempData(Garden garden, @Nullable Context context, TempUnit tempUnit, @Nullable LineChart chart, String[] additionalRef)
{
ArrayList vals = new ArrayList<>();
- ArrayList xVals = new ArrayList<>();
+ final ArrayList xVals = new ArrayList<>();
LineData data = new LineData();
float min = Float.MAX_VALUE;
float max = Float.MIN_VALUE;
@@ -567,7 +551,7 @@ public static void setTempData(Garden garden, @Nullable Context context, TempUni
double temperature = TempUnit.CELCIUS.to(tempUnit, ((TemperatureChange)action).getTemp());
- Entry entry = new Entry((float)temperature, index++);
+ Entry entry = new Entry(index++, (float)temperature);
entry.setData(action);
vals.add(entry);
xVals.add(date);
@@ -580,10 +564,12 @@ public static void setTempData(Garden garden, @Nullable Context context, TempUni
if (chart != null)
{
+ EntryExtKt.style(chart);
LineDataSet dataSet = new LineDataSet(vals, context.getString(R.string.stat_temerature));
styleDataset(context, dataSet, Color.parseColor(context.getResources().getStringArray(R.array.stats_colours)[0]));
- LineData lineData = new LineData(xVals, dataSet);
+ LineData lineData = new LineData(dataSet);
+
lineData.setValueFormatter(new ValueFormatter()
{
@Override public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler)
@@ -592,28 +578,12 @@ public static void setTempData(Garden garden, @Nullable Context context, TempUni
}
});
- chart.getAxisLeft().setAxisMinValue(min - 5f);
- chart.getAxisLeft().setAxisMaxValue(max + 5f);
styleGraph(chart);
chart.setData(lineData);
- chart.getAxisLeft().setValueFormatter(new YAxisValueFormatter()
- {
- @Override public String getFormattedValue(float value, YAxis yAxis)
- {
- return String.format("%s°%s", NumberUtilsKt.formatWhole(value), tempUnit.getLabel());
- }
- });
-
- chart.getAxisRight().setValueFormatter(new YAxisValueFormatter()
- {
- @Override public String getFormattedValue(float value, YAxis yAxis)
- {
- return String.format("%s°%s", NumberUtilsKt.formatWhole(value), tempUnit.getLabel());
- }
- });
chart.getXAxis().setYOffset(15.0f);
chart.setExtraOffsets(0, 0, 30, 0);
+ chart.getXAxis().setValueFormatter(new IndexAxisValueFormatter(xVals));
}
if (additionalRef != null)
@@ -665,7 +635,7 @@ public static void setHumidityData(Garden garden, @Nullable Context context, @Nu
double humidity = ((HumidityChange)action).getHumidity();
- Entry entry = new Entry((float)humidity, index++);
+ Entry entry = new Entry(index++, (float)humidity);
entry.setData(action);
vals.add(entry);
xVals.add(date);
@@ -678,10 +648,11 @@ public static void setHumidityData(Garden garden, @Nullable Context context, @Nu
if (chart != null && context != null)
{
+ EntryExtKt.style(chart);
LineDataSet dataSet = new LineDataSet(vals, "%");
styleDataset(context, dataSet, Color.parseColor(context.getResources().getStringArray(R.array.stats_colours)[2]));
- LineData lineData = new LineData(xVals, dataSet);
+ LineData lineData = new LineData(dataSet);
lineData.setValueFormatter(new ValueFormatter()
{
@Override public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler)
@@ -694,24 +665,10 @@ public static void setHumidityData(Garden garden, @Nullable Context context, @Nu
chart.getAxisLeft().setAxisMaxValue(max + 5f);
styleGraph(chart);
chart.setData(lineData);
- chart.getAxisLeft().setValueFormatter(new YAxisValueFormatter()
- {
- @Override public String getFormattedValue(float value, YAxis yAxis)
- {
- return (int)value + "%";
- }
- });
-
- chart.getAxisRight().setValueFormatter(new YAxisValueFormatter()
- {
- @Override public String getFormattedValue(float value, YAxis yAxis)
- {
- return (int)value + "%";
- }
- });
chart.getXAxis().setYOffset(15.0f);
chart.setExtraOffsets(0, 0, 30, 0);
+ chart.getXAxis().setValueFormatter(new IndexAxisValueFormatter(xVals));
}
if (additionalRef != null)
diff --git a/app/src/main/java/me/anon/lib/manager/PlantManager.kt b/app/src/main/java/me/anon/lib/manager/PlantManager.kt
index 17c159d1..86d519ba 100644
--- a/app/src/main/java/me/anon/lib/manager/PlantManager.kt
+++ b/app/src/main/java/me/anon/lib/manager/PlantManager.kt
@@ -59,7 +59,13 @@ class PlantManager private constructor()
}
synchronized(this.plants) {
- return ArrayList(plants.filterIndexed { index, plant -> garden?.plantIds?.contains(plant.id) ?: true })
+ val list = garden?.let {
+ ArrayList((garden?.plantIds ?: arrayListOf()).map { id ->
+ plants.firstOrNull { it.id == id }
+ }).filterNotNull() as ArrayList
+ } ?: plants
+
+ return list
}
}
@@ -85,11 +91,20 @@ class PlantManager private constructor()
override fun doInBackground(vararg params: Void?): Void?
{
plants.find { it.id == plant.id }?.let { plant ->
+ val parents = plant.images?.mapNotNull { File(it).parentFile } ?: arrayListOf()
+
// Delete images
plant.images?.forEach { filePath ->
File(filePath).delete()
}
+ parents.distinct().forEach {
+ var children = (it.list() ?: arrayOf())
+ if (children.firstOrNull()?.endsWith(".nomedia") == true) File(it, children.first()).delete()
+ children = (it.list() ?: arrayOf())
+ if (children.isEmpty()) it.delete()
+ }
+
// Remove plant
plants.removeAll { it.id == plant.id }
diff --git a/app/src/main/java/me/anon/lib/task/DecryptTask.java b/app/src/main/java/me/anon/lib/task/DecryptTask.java
index fa8a8057..cac3db2f 100644
--- a/app/src/main/java/me/anon/lib/task/DecryptTask.java
+++ b/app/src/main/java/me/anon/lib/task/DecryptTask.java
@@ -139,13 +139,13 @@ public DecryptTask(Context appContext)
if (values[1].equals(values[0]))
{
notification = new NotificationCompat.Builder(appContext, "export")
- .setContentText(appContext.getString(R.string.data_task))
- .setContentTitle(appContext.getString(R.string.task_complete))
+ .setContentText(appContext.getString(R.string.decrypt_task_complete))
+ .setContentTitle(appContext.getString(R.string.data_task))
.setContentIntent(PendingIntent.getActivity(appContext, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT))
- .setTicker(appContext.getString(R.string.task_complete))
+ .setTicker(appContext.getString(R.string.decrypt_task_complete))
.setSmallIcon(R.drawable.ic_floting_done)
- .setPriority(NotificationCompat.PRIORITY_LOW)
- .setAutoCancel(false)
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setAutoCancel(true)
.setOngoing(false)
.setSound(null)
.setProgress(0, 0, false);
diff --git a/app/src/main/java/me/anon/lib/task/EncryptTask.java b/app/src/main/java/me/anon/lib/task/EncryptTask.java
index d7b198d1..856caa85 100644
--- a/app/src/main/java/me/anon/lib/task/EncryptTask.java
+++ b/app/src/main/java/me/anon/lib/task/EncryptTask.java
@@ -139,13 +139,13 @@ public EncryptTask(Context appContext)
if (values[1].equals(values[0]))
{
notification = new NotificationCompat.Builder(appContext, "export")
- .setContentText(appContext.getString(R.string.data_task))
- .setContentTitle(appContext.getString(R.string.task_complete))
+ .setContentText(appContext.getString(R.string.encrypt_task_complete))
+ .setContentTitle(appContext.getString(R.string.data_task))
.setContentIntent(PendingIntent.getActivity(appContext, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT))
- .setTicker(appContext.getString(R.string.task_complete))
+ .setTicker(appContext.getString(R.string.encrypt_task_complete))
.setSmallIcon(R.drawable.ic_floting_done)
- .setPriority(NotificationCompat.PRIORITY_LOW)
- .setAutoCancel(false)
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setAutoCancel(true)
.setOngoing(false)
.setSound(null)
.setProgress(0, 0, false);
diff --git a/app/src/main/java/me/anon/lib/task/ImportTask.java b/app/src/main/java/me/anon/lib/task/ImportTask.java
index 593bf66e..7a8922c1 100644
--- a/app/src/main/java/me/anon/lib/task/ImportTask.java
+++ b/app/src/main/java/me/anon/lib/task/ImportTask.java
@@ -15,9 +15,12 @@
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
import javax.crypto.Cipher;
@@ -38,7 +41,7 @@
* @documentation // TODO Reference flow doc
* @project GrowTracker
*/
-public class ImportTask extends AsyncTask>, Integer, Void>
+public class ImportTask extends AsyncTask>, Integer, Void>
{
protected NotificationCompat.Builder notification;
protected NotificationManager notificationManager;
@@ -73,7 +76,7 @@ public ImportTask(Context appContext, AsyncCallback callback)
notificationManager.notify(1, notification.build());
}
- @Override protected Void doInBackground(Pair>... params)
+ @Override protected Void doInBackground(Pair>... params)
{
MainApplication.dataTaskRunning.set(true);
@@ -84,12 +87,17 @@ public ImportTask(Context appContext, AsyncCallback callback)
if (!to.exists())
{
to.mkdirs();
+ try
+ {
+ new File(to, ".nomedia").createNewFile();
+ }
+ catch (IOException e){}
}
ArrayList imagesToAdd = new ArrayList<>();
- for (Uri filePath : params[0].getSecond())
+ for (Uri filePath : params[0].getSecond().keySet())
{
- File toPath = new File(to, System.currentTimeMillis() + ".jpg");
+ File toPath = new File(to, params[0].getSecond().get(filePath) + ".jpg");
copyImage(appContext, filePath, toPath);
if (toPath.exists())
@@ -103,11 +111,15 @@ public ImportTask(Context appContext, AsyncCallback callback)
Plant plant = PlantManager.getInstance().getPlant(params[0].getFirst());
if (plant != null)
{
- plant.getImages().addAll(imagesToAdd);
+ ArrayList images = plant.getImages();
+ images.addAll(imagesToAdd);
+ Collections.sort(images);
+ plant.setImages(images);
+
PlantManager.getInstance().save();
}
- for (Uri uri : params[0].getSecond())
+ for (Uri uri : params[0].getSecond().keySet())
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
{
@@ -205,13 +217,13 @@ else if (imageUri.getScheme().startsWith("file"))
if (values[1].equals(values[0]))
{
notification = new NotificationCompat.Builder(appContext, "export")
- .setContentText(appContext.getString(R.string.data_task))
- .setContentTitle(appContext.getString(R.string.task_complete))
+ .setContentText(appContext.getString(R.string.import_task_complete))
+ .setContentTitle(appContext.getString(R.string.data_task))
.setContentIntent(PendingIntent.getActivity(appContext, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT))
- .setTicker(appContext.getString(R.string.task_complete))
+ .setTicker(appContext.getString(R.string.import_task_complete))
.setSmallIcon(R.drawable.ic_floting_done)
- .setPriority(NotificationCompat.PRIORITY_LOW)
- .setAutoCancel(false)
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setAutoCancel(true)
.setOngoing(false)
.setSound(null)
.setProgress(0, 0, false);
diff --git a/app/src/main/java/me/anon/model/Models.kt b/app/src/main/java/me/anon/model/Models.kt
index ae08287e..6e93e5d1 100644
--- a/app/src/main/java/me/anon/model/Models.kt
+++ b/app/src/main/java/me/anon/model/Models.kt
@@ -13,6 +13,8 @@ import me.anon.lib.TempUnit
import me.anon.lib.Unit
import me.anon.lib.ext.formatWhole
import me.anon.lib.helper.TimeHelper
+import org.threeten.bp.LocalDateTime
+import org.threeten.bp.format.DateTimeFormatter
import java.util.*
/**
@@ -46,17 +48,11 @@ class FeedingSchedule(
@Parcelize
@JsonClass(generateAdapter = true)
class FeedingScheduleDate(
- var id: String = UUID.randomUUID().toString(),
- var dateRange: Array,
- var stageRange: Array,
+ @Transient var id: String = UUID.randomUUID().toString(),
+ var dateRange: Array = arrayOf(),
+ var stageRange: Array = arrayOf(),
var additives: ArrayList = arrayListOf()
) : Parcelable {
- constructor() : this(
- id = UUID.randomUUID().toString(),
- dateRange = arrayOf(),
- stageRange = arrayOf(),
- additives = arrayListOf()
- ){}
}
abstract class Action(
@@ -77,7 +73,9 @@ abstract class Action(
TOP(R.string.action_topped, -0x6543555c, "Topped"),
TRANSPLANTED(R.string.action_transplanted, -0x65000073, "Transplanted"),
TRIM(R.string.action_trim, -0x6500546f, "Trim"),
- TUCK(R.string.action_tuck, -0x65800046, "ScrOG Tuck");
+ TUCK(R.string.action_tuck, -0x65800046, "ScrOG Tuck"),
+ SUPERCROP(R.string.action_supercrop, 0xFF72C7D6.toInt(), "Supercrop"),
+ MONSTERCROP(R.string.action_monstercrop, 0xFFFF7681.toInt(), "Monstercrop");
companion object
{
@@ -147,7 +145,7 @@ class StageChange(
@Parcelize
@JsonClass(generateAdapter = true)
class Plant(
- var id: String = UUID.randomUUID().toString(),
+ var id: String = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-hh-mm-ss")),
var name: String = "",
var strain: String? = null,
var plantDate: Long = System.currentTimeMillis(),
@@ -450,10 +448,12 @@ enum class PlantStage private constructor(val printString: Int, val enString: St
SEEDLING(R.string.seedling, "Seedling"),
CUTTING(R.string.cutting, "Cutting"),
VEGETATION(R.string.vegetation, "Vegetation"),
+ BUDDING(R.string.budding, "Budding"),
FLOWER(R.string.flowering, "Flowering"),
+ RIPENING(R.string.ripening, "Ripening"),
DRYING(R.string.drying, "Drying"),
CURING(R.string.curing, "Curing"),
- HARVESTED(R.string.harvested, "Harvested");
+ HARVESTED(R.string.harvested, "Harvested/Culled");
companion object
{
diff --git a/app/src/main/java/me/anon/view/FeedingDateHolder.kt b/app/src/main/java/me/anon/view/FeedingDateHolder.kt
index 0a66fb44..80394ee6 100644
--- a/app/src/main/java/me/anon/view/FeedingDateHolder.kt
+++ b/app/src/main/java/me/anon/view/FeedingDateHolder.kt
@@ -33,16 +33,21 @@ class FeedingDateHolder(val adapter: FeedingDateAdapter, itemView: View) : Recyc
copy.visibility = View.GONE
card.setCardBackgroundColor(R.attr.colorSurface.resolveColor(card.context))
- val lastStage = adapter.plantStages.toSortedMap().lastKey()
- val days = TimeHelper.toDays(adapter.plantStages[lastStage] ?: 0).toInt()
+ val lastStages = adapter.getLastStages()
- if (lastStage.ordinal >= feedingSchedule.stageRange[0].ordinal)
- {
- if (days >= feedingSchedule.dateRange[0]
- && ((days <= feedingSchedule.dateRange[1] && lastStage.ordinal == feedingSchedule.stageRange[0].ordinal)
- || (lastStage.ordinal < feedingSchedule.stageRange[1].ordinal)))
+ lastStages.forEachIndexed { index, lastStage ->
+ val days = TimeHelper.toDays(adapter.plantStages[index][lastStage] ?: 0).toInt()
+
+ if (lastStage.ordinal >= feedingSchedule.stageRange[0].ordinal)
{
- card.setCardBackgroundColor(android.R.attr.colorAccent.resolveColor(card.context))
+ if (days >= feedingSchedule.dateRange[0]
+ && ((days <= feedingSchedule.dateRange[1] && lastStage.ordinal == feedingSchedule.stageRange[0].ordinal)
+ || (lastStage.ordinal < feedingSchedule.stageRange[1].ordinal)))
+ {
+ itemView.tag = true
+ card.setCardBackgroundColor(android.R.attr.colorAccent.resolveColor(card.context))
+ return@forEachIndexed
+ }
}
}
diff --git a/app/src/main/res/layout-land/actions_list_view.xml b/app/src/main/res/layout-land/actions_list_view.xml
deleted file mode 100644
index 7e523d01..00000000
--- a/app/src/main/res/layout-land/actions_list_view.xml
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout-land/plant_details_view.xml b/app/src/main/res/layout-land/plant_details_view.xml
index 88cb3065..04d98c47 100644
--- a/app/src/main/res/layout-land/plant_details_view.xml
+++ b/app/src/main/res/layout-land/plant_details_view.xml
@@ -1,10 +1,11 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/app/src/main/res/layout-sw600dp/main_view.xml b/app/src/main/res/layout-sw600dp/main_view.xml
index b15d8954..f462a14f 100644
--- a/app/src/main/res/layout-sw600dp/main_view.xml
+++ b/app/src/main/res/layout-sw600dp/main_view.xml
@@ -44,7 +44,7 @@
diff --git a/app/src/main/res/layout/actions_list_view.xml b/app/src/main/res/layout/actions_list_view.xml
index a65ff46f..4ff34fb5 100644
--- a/app/src/main/res/layout/actions_list_view.xml
+++ b/app/src/main/res/layout/actions_list_view.xml
@@ -1,32 +1,17 @@
-
-
-
-
+
diff --git a/app/src/main/res/layout/add_watering_view.xml b/app/src/main/res/layout/add_watering_view.xml
index 8ff88e0f..8efaca33 100644
--- a/app/src/main/res/layout/add_watering_view.xml
+++ b/app/src/main/res/layout/add_watering_view.xml
@@ -194,6 +194,45 @@
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
diff --git a/app/src/main/res/layout/chart_marker.xml b/app/src/main/res/layout/chart_marker.xml
index 2d73540f..621e08db 100644
--- a/app/src/main/res/layout/chart_marker.xml
+++ b/app/src/main/res/layout/chart_marker.xml
@@ -5,22 +5,23 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="4dp"
- app:cardBackgroundColor="#40000000"
+ app:cardBackgroundColor="#F0000000"
app:cardCornerRadius="4dp"
>
diff --git a/app/src/main/res/layout/data_label_stub.xml b/app/src/main/res/layout/data_label_stub.xml
index 9407e5a9..73d0402b 100644
--- a/app/src/main/res/layout/data_label_stub.xml
+++ b/app/src/main/res/layout/data_label_stub.xml
@@ -5,7 +5,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:minWidth="160dp"
+ android:minWidth="120dp"
>
+
+
diff --git a/app/src/main/res/layout/fragment_holder.xml b/app/src/main/res/layout/fragment_holder.xml
index 19acf0fa..70a078a7 100644
--- a/app/src/main/res/layout/fragment_holder.xml
+++ b/app/src/main/res/layout/fragment_holder.xml
@@ -25,9 +25,9 @@
/>
-
diff --git a/app/src/main/res/layout/garden_fragment_holder.xml b/app/src/main/res/layout/garden_fragment_holder.xml
index 25a4ac36..753147c4 100644
--- a/app/src/main/res/layout/garden_fragment_holder.xml
+++ b/app/src/main/res/layout/garden_fragment_holder.xml
@@ -6,20 +6,13 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
>
-
-
-
+ />
-
-
-
+
+
-
-
-
-
-
-
-
-
+ android:gravity="center_horizontal"
+ android:text="@string/empty_plant_list"
+ android:textColor="?android:textColorTertiary"
+ android:layout_marginTop="12dp"
+ android:layout_gravity="center_horizontal"
+ />
+
+
-
+
diff --git a/app/src/main/res/layout/image_grid_view.xml b/app/src/main/res/layout/image_grid_view.xml
index b5b4a656..51b55ed0 100644
--- a/app/src/main/res/layout/image_grid_view.xml
+++ b/app/src/main/res/layout/image_grid_view.xml
@@ -1,46 +1,52 @@
-
-
-
-
-
-
-
+ android:id="@+id/empty"
+ android:layout_gravity="center"
+ android:orientation="vertical"
+ android:visibility="gone"
+ >
+
+
+
+
+
-
+
diff --git a/app/src/main/res/layout/note_dialog.xml b/app/src/main/res/layout/note_dialog.xml
index 97af38b2..32d3f2f5 100644
--- a/app/src/main/res/layout/note_dialog.xml
+++ b/app/src/main/res/layout/note_dialog.xml
@@ -7,6 +7,23 @@
android:orientation="vertical"
android:padding="16dp"
>
+
+
+
+
-
-
-
-
+
+
-
+
+
-
+
+
+ android:paddingLeft="12dp">
+
+
+
+
+
+
+
+ android:layout_marginLeft="12dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginRight="12dp" />
-
-
+ android:layout_gravity="end"
+ android:layout_marginEnd="8dp"
+ android:padding="16dp"
+ android:text="@string/feed_again"
+ android:textColor="?android:textColorPrimary" />
+
+
-
+
-
-
-
+ android:text="@string/plant_details_title" />
-
-
-
-
-
+ android:showDividers="middle">
-
-
+ android:orientation="vertical"
+ android:paddingLeft="16dp"
+ android:paddingTop="8dp"
+ android:paddingRight="16dp"
+ android:paddingBottom="16dp">
-
-
+
-
+
+
+
+ android:orientation="vertical"
+ android:paddingLeft="16dp"
+ android:paddingTop="16dp"
+ android:paddingRight="16dp"
+ android:paddingBottom="16dp">
+
+
+
+
+
-
-
-
-
-
-
+ android:layout_marginTop="12dp"
+ android:elevation="4dp"
+ android:orientation="vertical">
-
-
+
-
+ android:divider="?android:listDivider"
+ android:orientation="vertical"
+ android:showDividers="middle">
-
+ android:orientation="vertical"
+ android:padding="16dp">
+
+
+
+
+
+
+
+
+
+
-
+ android:showDividers="middle">
-
-
+ android:background="?selectableItemBackground"
+ android:orientation="vertical"
+ android:padding="16dp">
-
-
+
-
+
+
+
-
-
-
+ android:background="?selectableItemBackground"
+ android:orientation="vertical"
+ android:padding="16dp">
-
-
+
-
-
-
+
+
+
+ android:orientation="vertical"
+ android:padding="16dp">
-
+
+
+
+
+
+ android:orientation="horizontal"
+ android:paddingLeft="16dp"
+ android:paddingTop="16dp"
+ android:paddingRight="16dp"
+ android:paddingBottom="8dp">
+
+
+
+
+
+
+
-
-
+
+
diff --git a/app/src/main/res/layout/plant_list_view.xml b/app/src/main/res/layout/plant_list_view.xml
index 3b1163e5..10a0ac0b 100644
--- a/app/src/main/res/layout/plant_list_view.xml
+++ b/app/src/main/res/layout/plant_list_view.xml
@@ -1,59 +1,65 @@
-
-
-
-
-
-
-
-
-
+ android:id="@+id/empty"
+ android:layout_gravity="center"
+ android:orientation="vertical"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ >
+
+
+
+
+
+
-
+
diff --git a/app/src/main/res/layout/schedule_details_view.xml b/app/src/main/res/layout/schedule_details_view.xml
index 4fa08ada..c994c169 100644
--- a/app/src/main/res/layout/schedule_details_view.xml
+++ b/app/src/main/res/layout/schedule_details_view.xml
@@ -1,161 +1,167 @@
-
-
-
-
-
+
+
-
+
+
-
+
+
-
-
-
+
+
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
+
diff --git a/app/src/main/res/layout/schedule_list_view.xml b/app/src/main/res/layout/schedule_list_view.xml
index 489f334c..eb1f3b12 100644
--- a/app/src/main/res/layout/schedule_list_view.xml
+++ b/app/src/main/res/layout/schedule_list_view.xml
@@ -1,48 +1,56 @@
-
-
-
-
-
-
-
+ android:id="@+id/empty"
+ android:layout_gravity="center"
+ android:orientation="vertical"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ >
+
+
+
+
+
-
+
diff --git a/app/src/main/res/layout/statistics2_view.xml b/app/src/main/res/layout/statistics2_view.xml
new file mode 100644
index 00000000..ce90ec26
--- /dev/null
+++ b/app/src/main/res/layout/statistics2_view.xml
@@ -0,0 +1,275 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/subtitle_stub.xml b/app/src/main/res/layout/subtitle_stub.xml
new file mode 100644
index 00000000..45b4d6dc
--- /dev/null
+++ b/app/src/main/res/layout/subtitle_stub.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/tabbed_fragment_holder.xml b/app/src/main/res/layout/tabbed_fragment_holder.xml
index e91a3dde..4b80d807 100644
--- a/app/src/main/res/layout/tabbed_fragment_holder.xml
+++ b/app/src/main/res/layout/tabbed_fragment_holder.xml
@@ -25,10 +25,10 @@
/>
-
@@ -38,7 +38,7 @@
-
+
+
+
+
+
-
+
-
+
+
+ Verze %s
+ Nastavení
+
+
+ %srostlin
+
+ Všechny rostliny
+
+ Vše
+ Akce přidána
+ Poznámka přidána
+ Zálivka přidána
+ Zahrada vymazána
+
+ Jste si jistí, že chcete vymazat zahradu <b>\"%s\"</b>? Tímto nesmažete rostliny.
+ Zálivka
+ Akce
+
+ Widget není dostupný když je zapnuto nastavení šifrování.
+ Přidat novou rostlinu
+ Podrobnosti o rostlině
+ Statistiky rostliny
+
+ Z fotoaparátu
+
+ Z galerie
+ Vyberte možnost
+ Vyberte obrázek
+ Přidat k další rostlině
+ Vybrat rostlinu
+ Obrázek přidán
+ Vyfotit další
+
+ Rozvrhy
+ Podrobnosti rozvrhu
+ Datum rozvrhu
+ Název
+ Lehká výživa
+ Popis
+
+ Lehká výživa s proplachem každé 2 týdny
+
+ + Nový plán výživy
+ + Nový rozvrh
+ + Nové aditivum
+ Aditiva
+ Aditivum
+ Od data
+ Od fáze
+ Do data
+ Do fáze
+ Vybrat z plánu
+ Vybrat z minulých
+ Jste si jisti?
+ Kopírovat vybraný plán?
+ Smazat vybraný plán?
+ Plán zkopírován
+ Plán vymazán
+ Vrátit
+ Plány výživy
+ Plán výživy
+ Aplikace byla updatována
+ Aplikace byla povýšena na verzi %s
+ Prohlédnout změny
+ Zahodit
+
+
+ Zálivka %s
+ Zalít více rostlin
+
+ Poznámky
+ Podrobnosti o zálivce
+ pH zálivky
+ PPM
+ Odtok
+ Množství (%s)
+ Teplota (º%s)
+ Datum a čas
+ Nyní
+ Datum
+ Dnes
+ Pomalé sušení %sdní
+
+ Zálivky
+ Změny fáze
+
+ Duplikovat
+ Kopírovat do
+ Upravit akci
+ Vymazat akci
+
+ Vybrat rostliny
+ Zahrady
+ Všechny rostliny
+ + Nová zahrada
+ Ostatní
+ Nastavení
+ Vymazat
+ Sdílet
+ Foto
+ Poslední zálivka
+ Zalít znovu
+
+ Jméno rostliny
+ Nová rostlina
+ Odrůda rostliny
+ Lemon haze
+ Podrobnosti
+ Datum zasazení
+ Substrát
+
+ Podrobnosti substrátu
+
+ Zemina, perlit 50/50 mix
+ Z klonu?
+ Fáze růstu - podrobnosti
+ Fáze rostliny
+ Zobrazit fotky
+ Zobrazit historii
+ Zobrazit statistiky
+ Fotky
+ Historie
+ Statistiky
+ Filtr
+ Zasazeno
+ Klíčení
+ Řízkování
+ Růstová fáze
+ Tvorba květu
+ Květová fáze
+ Zrání
+ Sušení
+ Konzervace
+ Sklizeno
+
+ Sklizeno před %s
+ Exportovat zahradu
+ Upravit zahradu
+ Vymazat zahradu
+ Exportovat
+
+ Celkový čas:
+ Počet zálivek:
+ Počet proplachů:
+ Celkem vody:
+ Prům. spotřeba vody:
+ Průměrný čas mezi zálivkami:
+ Prům. %s vody:
+ Filtrovat přísady
+ pH
+ TDS
+
+ Min.:
+ Max:
+ Průměr:
+ Teplota
+ Obecné
+ Nepodařilo se načíst rostlinu
+ FIM
+ Proplach
+ Postřik
+ LST
+ Lollipop
+ Aplikace pesticidů
+ Topováno
+ Přesazeno
+ Trim
+ Scrog Tuck
+ Supercrop
+ Monstercrop
+
+ Zalito
+ Poznámka
+ Vymazat tuto událost?
+ Jste si jisti, že chcete vymazat
+ Jste si jisti, že chcete vymazat <b>%s</b> obrázky? Toto nelze vrátit.
+ Upravit
+ fáze
+ Nastavit
+ Změnit
+ Přidat
+ Zrušit
+ akce
+ OK
+ Nutno vyplnit pole
+ Chyba - nesprávné heslo
+ Upravit zálivku
+ přidáno
+ Akce duplikována
+ více rostlin
+ Akce přidána k
+ Poznámka upravena
+ upraveno
+ Fáze změněna
+ Událost vymazána
+ Hotovo
+ Zahrada
+ Vybrat vše
+ Nevybrat nic
+ Potvrdit
+ Zapnout
+ Odmítnout
+ Klíček:
+ Klíček
+
+ Jejda
+ Zdá se, že při posledním použití aplikace nastala chyba. Chcete poslat anonymní hlášení? Tato hlášení budou poslána na lt;a href=\"https://github.com/7LPdWcaW/GrowTracker-Android/issues\">github.com/7LPdWcaW/GrowTracker-Android/issues</a>, žádné osobní informace nebudou přiloženy. Nebo můžete zaslat tato hlášení ručně na <a href=\"https://reddit.com/r/growutils\">reddit.com/r/growutils</a> , pokud chcete. Reporty jsou uloženy v <i>%s<i>
+ Ano
+ Ne
+
+ Zadejte vaše heslo
+ Zadejte heslo
+ Znovu zadejte vaše heslo
+ Chyba - hesla se neshodují
+
+ Neočekávaná chyba při ukládání dat zahrady, prosím zazálohujte tato data
+ Vzato
+ Před %s
+ Zalito před <b>%s</b>
+ Naposledy zalito před <b>%s</b>
+ Zasazeno před <b>%s</b>
+
+ Přístup k externí paměti je vyžadován k ukládání obrázku. Žádný další prostor není využíván.
+
+ Zálivka přidána
+ Chystáte se vymazat <b>%s</b> a všechny přidružené obrázky. Jste si jisti? Tuto akci nelze vrátit.
+ Vymazávám rostlinu...
+
+ Rostlina duplikována
+ Otevřít
+
+ Exportuji pěstební data...
+ Exportuji pěstební data celé zahrady...
+ %spřídáno
+ Jméno nemůže být prázdné
+ Exportuji %s
+
+ Zemina
+ Hydroponie
+ Kokos
+ Aeroponie
+ Skrýt obrázky
+ Ukázat obrázky
+
+ Výchozí zahrada k zobrazení při otevření, aktuálně: <b>%s</b>
+ Vyberte styl karty, aktuálně: %s
+ Výchozí jednotky pro vodu, aktuálně: <b>%slt;/b>
+ Výchozí jednotky pro přísady, aktuálně: <b>%slt;/b>
+ Výchozí jednotky pro teplotu, aktuálně: <b>%s</b>
+ Aktuálně: <b>%sMb</b> / Využívám <b>%s</b>
+ Zadejte toto heslo během dešifrovací fáze abyste zabránili zobrazení dat.
+ Výstraha
+ Toto je základní AES šifrování založené na zadaném hesle. Nejedná se o zaručenou formu zabezpečení před úřady.
+ Zálohování zapnuto, zálohy budou uloženy v %s
+ Obnovení do %s hotovo.
+ Nebylo možné obnovit ze zálohy %s. Soubor může být %s
+ šifrováno
+ nešifrováno
+
+ Vybrat míru
+ Vybrat teplotu
+ Vybrat zahradu
+ Vybrat zálohu
+ Musíte mít zapnuté šifrování se stejným heslem k obnově této zálohy.
+
+ Zálohováno do
+ Nejsou tu žádné zálohy k obnovení
+ Vše/nic
+
+ Vstupní pH
+ pH odtoku
+ Průměrné pH
+ Průměrná teplota
+ Průměrné pH odtoku
+ Průměr %s
+
+ Obrázky rostliny
+
+ pH do:
+ pH ven:
+ Množství
+ Teplota:
+ Přísady:
+ EC:
+ PPM:
+
+ Vybrat plán:
+ Akce
+ Vynutit tmavý režim
+ Vynutí tmavý režim aplikace nezávisle na nastavení systému.
+ Schovat sklizené
+ Nezobrazí všechny rostliny, které byly sklizené
+ Změnit pořadí rostlin
+ Zobrazí rostliny v obráceném pořadí. (Nutný restart aplikace)
+ Výchozí zahrada
+
+ Jednotky
+ Jednotky pro vodu
+ Jednotky pro aditiva
+ Jednotky teploty
+ TDS jednotky
+ Výchozí jednotky TDS, aktuálně <b>%slt;/b>
+ Správa dat
+ Automatická záloha
+ Automaticky zálohuje data rostlin každých 24 hodin
+ Zálohovat hned
+ Maximální velikost zálohy
+ Velikost v Mb
+ Limit zálohy (Mb)
+ Obnovit ze zálohy
+ Šifrovat data
+ Přidat zámek k aplikaci a šifrovat veškerá data a obrázky
+ Bezpečnostní pojistka
+ Nastavte bezpečnostní pojistku abyste zabránili přístupu k datům
+ Doplňky
+ O aplikaci
+ Čti mne
+ Exportovat data
+ Styl karty
+ Nejsou tu žádné rostliny k zobrazení
+ Nejsou tu žádné plány
+ Nejsou tu žádné obrázky k zobrazení
+ Minulé akce
+ Zobrazit/skrýt kalendář
+
+ %dvybráno
+ Děkuji následujícím
+ Překlady
+ Pomozte překládat
+ Šifruji data a obrázky, může to chvilku trvat...
+ Dešifruji data a obrázky, může to chvilku trvat...
+
+
+ 1s
+ s
+ m
+ h
+ d
+ t
+ měs.
+ rok
+
+ Podrobnosti
+
+
+
- vteřina
+ - vteřiny
+ - minuty
+ - vteřiny
+
+
+
+ - minuta
+ - minuty
+ - minuty
+ - minuty
+
+
+
+ - hodina
+ - hodiny
+ - hodin
+ - hodin
+
+
+
+ - den
+ - dny
+ - dnů
+ - dny
+
+
+
+ - týden
+ - týdny
+ - týdnů
+ - týdny
+
+
+
+ - měsíc
+ - měsíce
+ - měsíců
+ - měsíce
+
+
+
+ - rok
+ - roky
+ - roků
+ - roky
+
+
+
+ - Původní
+ - Kompaktní
+ - Extrémní
+
+
+ Ztratíte všechny neuložené změny
+ Zavřít
+ Rostliny
+ Vlhkost
+ Aktuální teplota:
+ Aktuální vlhkost:
+ Nastavení osvětlení
+ Vymazat tuto položku?
+ Jste si jistí, že chcete vymazat <b>%s</b>?
+ Zobrazit
+ Skrýt
+ Světla zapnuta
+ Světla vypnuta
+
+ Naposledy zálohováno: <b>%s</b>
+ Umístění obrázků, aktuálně <b>%s</b>
+ Nepodařilo se nastavit umístění obrázků
+ Umístění obrázků
+ Vymazat cache obrázků
+ Obrázky a cache byly vymazány
+ Importuji obrázky, může to chvilku trvat...
+ Zadání
+ Import obrázků hotov
+ Dešifrování obrázků dokončeno
+ Šifrování obrázků hotovo
+ Nejsou tu žádné akce k zobrazení
+
+ Exportuji pěstební data %s
+ Export %s hotov
+ Exportováno %sdo%s
+ Export hotov
+ Včetně obrázků?
+ Žádná data k dispozici
+
+ Je potřeba oprávnění k fotoaparátu pro focení
+ Nastavení zálohování
+ Umístění záloh
+ Umístění záloh, aktuálně <b>%s</b>
+ Nepodařilo se nastavit umístění záloh
+ Nepodařilo se importovat rostliny ze souboru
+ Importovat rostliny ze souboru
+ Rostliny úspěšně importování ze souboru
+
+ %sstatistiky
+ Prům. na hnojení:
+ Počet použití:
+ Celkem přísad:
+
+ Koncentrace přísad
+ Přísady v průběhu
+ Rozdělení přísad
+
diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml
new file mode 100644
index 00000000..173f6e7f
--- /dev/null
+++ b/app/src/main/res/values-de-rDE/strings.xml
@@ -0,0 +1,440 @@
+
+
+
+ Version %s
+ Einstellungen
+
+
+ %s Pflanzen
+
+ Alle Pflanzen
+
+ Alle
+ Maßnahme hinzugefügt
+ Notiz hinzugefügt
+ Bewässerung hinzugefügt
+ Garten gelöscht
+
+ Willst du Garten <b>\"%s\"</b> löschen? Dadurch werden die Pflanzen nicht gelöscht.
+ Gießen
+ Maßnahme
+
+ Widget ist nicht verfügbar, wenn die Verschlüsselung aktiviert ist
+ Neue Pflanze hinzufügen
+ Pflanzen Details
+ Pflanzen Statistik
+
+ Bild aufnehmen
+
+ Aus der Galerie
+ Option auswählen
+ Bild auswählen
+ Auf eine andere Pflanze anwenden
+ Pflanze auswählen
+ Bild hinzugefügt
+ Weiteres hinzufügen
+
+ Zeitpläne
+ Zeitplan Details
+ Plane ein Datum
+ Name
+ Geringfügiges Düngen
+ Beschreibung
+
+ leichtes Düngen mit 2-wöchigem Ausspülen
+
+ + Neues Düngen
+ Neuer Zeitplan
+ + neuer Zusatzstoff
+ Zusatzstoffe
+ Zusatzstoff
+ Startzeitpunkt
+ Von Stadium
+ Endzeitpunkt
+ Zu Stadium
+ Aus Zeitplan befüllen
+ Aus Vorigem füllen
+ Bist du sicher?
+ Ausgewählten Zeitplan kopieren?
+ Ausgewählten Zeitplan löschen?
+ Zeitplan kopiert
+ Zeitplan gelöscht
+ Rückgängig
+ Düngepläne
+ Düngeplan
+ App aktualisiert
+ App wurde auf Version %s aktualisiert
+ Änderungen ansehen
+ Ablehnen
+
+
+ Gieße %s
+ Gieße mehrere Pflanzen
+
+ Notizen
+ Bewässerungsdetails
+ Wasser pH
+ PPM
+ Abfluss
+ Menge (%s)
+ Temperatur (º%s)
+ Datum und Zeit
+ Jetzt
+ Darum
+ Heute
+ %s Tage fermentiert
+
+ Bewässerungen
+ Stadiumsänderungen
+
+ Duplizieren
+ Kopieren nach
+ Maßnahme bearbeiten
+ Maßnahme löschen
+
+ Pflanzen auswählen
+ Gärten
+ Alle Pflanzen
+ + Neuer Garten
+ Weitere
+ Einstellungen
+ Löschen
+ Teilen
+ Foto
+ Zuletzt gegossen
+ Nochmal gießen
+
+ Pflanzen Name
+ Neue Pflanze
+ Pflanzensorte
+ Lemon haze
+ Pflanzdetails
+ Pflanzzeitpunkt
+ Substrat
+
+ Substratdetails
+
+ Erde, Perlite Mischung 50/50
+ Aus Klon?
+ Stadiumsdetails
+ Pflanzenstadium
+ Fotos ansehen
+ Verlauf ansehen
+ Statistik ansehen
+ Fotos
+ Verlauf
+ Statistik
+ Filter
+ Gepflanzt
+ Keimung
+ Schneiden
+ Vegetation
+ Vorblüte
+ Blütezeit
+ Reifung
+ Trocknen
+ Fermentation
+ Geernetet
+
+ Vor %s geerntet
+ Garten exportieren
+ Garten bearbeiten
+ Garten löschen
+ Exportieren
+
+ Gesamtzeit:
+ Insgesamt gegossen:
+ Gesamt-Ausspülungen:
+ Gesamtwassermenge:
+ Durchschnittliche Wassermenge
+ Mittlere Zeit zwischen dem Gießen:
+ Durchschnittlich %s Wasser:
+ Filter-Zusatzstoffe
+ PH
+ TDS
+
+ Min:
+ Max:
+ Ø:
+ Temperatur
+ Allgemein
+ Pflanze konnte nicht geladen werden
+ vergessen
+ Ausspülen
+ Blattdüngung
+ Stressarmes Training
+ Lutscher
+ Pestizidanwendung
+ Maximum erreicht
+ umgepflanzt
+ Trimmen
+ ScrOG Tuck
+ Super-Ernte
+ Monster-Ernte
+
+ Gegossen
+ Notiz
+ Dieses Ereignis löschen?
+ Möchtest du das wirklich löschen?
+ Möchtest du diese <b>%s</b> Bilder wirklisch löschen? Sie können danach nicht wiederhergestellt werden.
+ Bearbeiten
+ Stadium
+ Setzen
+ Ändern
+ Hinzufügen
+ Abbrechen
+ Aktion
+ OK
+ Feld muss ausgefüllt werden
+ Fehler - falsches Passwort
+ Bewässerung bearbeiten
+ hinzugefügt
+ Maßnahme dupliziert
+ mehrere Pflanzen
+ Maßnahme hinzugefügt zu
+ Notiz aktualisiert
+ aktualisiert
+ Stadium aktualisiert
+ Ereignis gelöscht
+ Erledigt
+ Garten
+ Alles auswählen
+ Nichts auswählen
+ Akzeptieren
+ Aktivieren
+ Ablehnen
+ Sämling:
+ Sämling
+
+ Ups
+ Offenbar ist die App bei der letzten Nutzung gelöscht. Möchtest du einen anonymen Fehlerreport senden? Dieser Fehlerreport wird an <a href=\"https://github.com/7LPdWcaW/GrowTracker-Android/issues\">github.com/7LPdWcaW/GrowTracker-Android/issues</a> gesemdet und es werden keine persönlichen Informationen gesendet. Zusätzlich kannst du den Fehler hier melden: <a href=\"https://reddit.com/r/growutils\">reddit.com/r/growutils</a> auf Wunsch auch manuell. Die Fehlerreporte werden hier gespeichert: <i>%s<i>
+ Ja
+ Nein
+
+ Passwort eingeben
+ Ein Passwort eingeben
+ Passwort erneut eingeben
+ Fehler - Passwörter stimmen nicht überein
+
+ Der Garten konnte nicht gespeichert werden, bitte mach ein Backup deiner Daten.
+ Entnommen
+ Vor %s
+ Gegossen vor <b> %s </b>
+ Zuletzt gegossen vor <b> %s </b>
+ Gepflanzt vor <b> %s </b>
+
+ Für die Speicherung von Fotos ist der Zugriff auf den externen Speicher erforderlich. Es werden keine anderen Daten verwendet.
+
+ Bewässerungen hinzugefügt
+ Hiermit wird <b> %s </b> gelöscht, ebenso alle damit verknüpften Bilder, sind sie sicher? Dies kann nicht wieder rückgängig gemacht werden.
+ Lösche Pflanze...
+
+ Pflanze dupliziert
+ Öffnen
+
+ Exportiere Wachstumsprotokoll
+ Exportiere Wachstumsprotokoll des Gartens…
+ %s hinzugefügt
+ Name kann nicht leer sein
+ Exportiere %s
+
+ Erde
+ Hydroponik
+ Kokosnussfasern
+ Aeroponik
+ Bild verstecken
+ Bild anzeigen
+
+ Standard-Garten beim Öffnen, aktuell: <b>%s</b>
+ Kartenstil wählen, gegenwärtig: %s
+ Standardmäßig zu verwendende Maßeinheit, aktuell: <b>%s</b>
+ Standardmäßig zu verwendende Maßeinheit für Dünger, aktuell: <b>%s</b>
+ Standardmäßig zu verwendende Temperatureinheit, aktuell: <b>%s</b>
+ Aktuell: <b>%s MiB</b> / Benutzt <b>%s</b>
+ Gib das Passwort während der Entschlüsselungsphase an, um das Laden von Daten zu verhindern
+ Warnung
+ Dies ist eine Standart AES-Verschlüsselung auf der Grundlage einer bereitgestellten Passphrase. Es ist kein garantierter Schutz vor Strafverfolgungsbehörden.
+ Backup aktiviert, die Backups werden in %s
+ Wiederherstellen nach %s abgeschlossen
+ Konnte Sicherung %s nicht wiederherstellen. Datei könnte %s sein.
+ verschlüsselt
+ nicht verschlüsselt
+
+ Messung auswählen
+ Temperatur auswählen
+ Garten auswählen
+ Backup auswählen
+ Du musst den verschlüsselten Modus mit demselben Kennwort aktiviert haben, um dieses Backup wiederherzustellen
+
+ Gesichert nach
+ Es ist kein Backup vorhanden
+ Alle/Keine
+
+ hinzugefügter pH-Wert
+ Abfluss pH
+ Durchschnitts pH-Wert
+ Durchschnittliche Temperatur
+ Durchschnittlicher pH Abfluss
+ Durchschnittlich %s
+
+ Pflanzenfotos
+
+ Start pH-Wert
+ End pH-Wert
+ Menge:
+ Temp:
+ Zusatzstoffe:
+ EC:
+ PPM:
+
+ Zeitplan auswählen
+ Maßnahmen
+ Erzwinge Dark Theme
+ Erzwingt das Dark Theme unabhängig von der Systemeinstellung
+ Geerntete verstecken
+ Verstecke alle Pflanzen, die geerntet wurden
+ Reihenfolge der Anlage umkehren
+ Zeigt Pflanzen in umgekehrter Reihenfolge. (Benötigt App-Neustart)
+ Standard-Garten
+
+ Einheiten
+ Lieferumfang
+ Maßeinheit
+ Temperatureinheiten
+ TDS Einheit
+ Standardmäßig zu verwendende TDS Einheit, aktuell: <b>%s</b>
+ Datenmanagement
+ Automatisches Backup
+ Sichert Automatisch Ihre Daten alle 24 Stunden
+ Jetzt sichern
+ Begrenzung der Backup-Größe
+ Größe in MB
+ Backup-Limit (MB)
+ Wiederherstellen aus Backup
+ Daten verschlüsseln
+ Fügt eine PIN-Sperre hinzu und verschlüsselt alle Daten/Bilder
+ Ausfallsicher
+ Setze ein sicheres Kennwort, um den Zugriff auf Daten zu verhindern
+ Erweiterungen
+ Über
+ Lies mich
+ Daten exportieren
+ Kartenstil
+ Keine Pflanzen zum Anzeigen vorhanden
+ Es gibt keine Zeitpläne
+ Keine Fotos zum Anzeigen vorhanden
+ Vergangene Maßnahmen
+ Kalender ein-/ausblenden
+
+ %d ausgewählt
+ Vielen Dank an
+ Übersetzungen von
+ Hilf Übersetzen
+ Das Verschlüsseln von Daten und Bildern kann eine Weile dauern...
+ Das Entschlüsseln von Daten und Bildern kann eine Weile dauern...
+
+
+ 1s
+ s
+ m
+ h
+ d
+ w
+ mo
+ y
+
+ Details
+
+
+ - Sekunde
+ - Sekunden
+
+
+
+ - Minute
+ - Minuten
+
+
+
+ - Stunde
+ - Stunden
+
+
+
+ - Tag
+ - Tage
+
+
+
+ - Woche
+ - Wochen
+
+
+
+ - Monat
+ - Monate
+
+
+
+ - Jahr
+ - Jahre
+
+
+
+ - Original
+ - Kompakt
+ - Extreme
+
+
+ Du verlierst alle ungespeicherten Änderungen
+ Beenden
+ Pflanzen
+ Feuchtigkeit
+ Aktuelle Temperatur:
+ Aktuelle Feuchtigkeit:
+ Beleuchtungsplan
+ Diesen Eintrag löschen?
+ Willst du <b>%s</b> wirklich löschen?
+ Anzeigen
+ Verstecken
+ Licht an
+ Licht aus
+
+ Letzte Sicherung am: <b>%s</b>
+ Bilder-Speicherort, derzeit: <b>%s</b>
+ Bildspeicherort konnte nicht festgelegt werden
+ Speicherort des Bildes
+ Bild-Cache löschen
+ Image-Festplatte und Speicher-Cache gelöscht
+ Das Importieren von Bildern kann eine Weile dauern...
+ Daten Aufgabe
+ Bildimport abgeschlossen
+ Bildentschlüsselung abgeschlossen
+ Bildverschlüsselung abgeschlossen
+ Es sind keine Aktionen zu zeigen
+
+ Exportiere Wachstumsprotokoll von %s
+ Export von %s abgeschlossen
+ Exportiert %s nach %s
+ Export abgeschlossen
+ Bilder einbeziehen?
+ Es sind keine Daten verfügbar
+
+ Zum Fotografieren ist die Kameraberechtigung erforderlich
+ Backup-Verwaltung
+ Backup-Speicherort
+ Backup-Speicherort, derzeit <b>%s</b>
+ Backup-Speicherort konnte nicht festgelegt werden
+ Import von Pflanzen aus Datei fehlgeschlagen
+ Pflanzen aus Datei importieren
+ Pflanzen erfolgreich aus Datei importiert
+
+ %s Statistiken
+ Durchschnitt pro Düngung:
+ Wie oft verwendet:
+ Gesamtzusatzstoffe:
+
+ Zusatzstoffkonzentration
+ Zusatzstoffe im Zeitablauf
+ Zusatzstoffverteilung
+
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 8541d2c8..ffdf5dd9 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -129,7 +129,9 @@
Germinación
Esqueje
Vegetación
+ Cerniendo
Florecimiento
+ Madurado
Secado
Curando
Cosechado
@@ -146,14 +148,13 @@
Tiempo promedio entre regados:
Filtrar aditivos
PH
-
Mín:
Máx:
Promedio:
Temperatura
General
Falló al cargar la planta
- Mierda, se me olvidó (FIM)
+ FIM
Desagotar
Alimentación foliar
Entrenamiento de bajo stress
@@ -163,6 +164,8 @@
Transplantado
Recortar
Meter debajo de follaje preexistente
+ Supercrop
+ Monstercrop
Regado
Nota
@@ -246,7 +249,7 @@
Provee esta contraseña durante la desencriptación para prevenir que la información se cargue
Alerta
Esta es una forma básica de encriptación AES, basado en una frase contraseña. Esto no es una defensa garantizada contra organismos de justicia.
- Copia de seguridad habilitada, copias se guardaran en /sdcard/backups/GrowTracker/
+ Copia de seguridad habilitada, copias se guardaran en /sdcard/backups/GrowTracker/
Restauración a %s completada
No se pudo restaurar desde la copia de seguridad %s. El archivo puede ser %s
encriptado
@@ -265,7 +268,6 @@
PH de entrada
Perdida de PH
PH promedio
-
Fotos de la planta
PH de entrada:
@@ -408,4 +410,12 @@
No hay ninguna información disponible
Para tomar fotos se necesita el permiso para la cámara
-
+ Manejo de copias de seguridad
+ Lugar de guardadado de la copia de seguridad
+ Ubicación de la copia de seguridad, actualmente <b>%s</b>
+ Falló en establecer ubicación de la copia de seguridad
+ Falló al importar plantas desde archivo
+ Importar plantas desde archivo
+ Plantas importadas de archivo correctamente
+
+
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index b8306f06..067f20ea 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -146,14 +146,13 @@
Temps moyen entre arrosage
Filtrer les engrais et additifs
pH
-
Min:
Max:
Moy:
Temperature
Général
Echec lors du chargement des plantes
- Putain j\'ai oublié (PJO)
+ PJO
Rinçage
Arrosage foliaire
Palissage
@@ -163,7 +162,6 @@
Rempotage
Taillée
Tricotage ScrOG
-
Arrosée
Note
Supprimer cet événement ?
@@ -265,7 +263,6 @@
pH de la solution nutritive
pH du surplus
pH moyen
-
Photos de la plante
pH interne :
@@ -415,4 +412,5 @@
Impossible d\'importer les plantes depuis le fichier
Importer des plantes depuis un fichier
Plantes bien importées depuis le fichier
-
+
+
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
new file mode 100644
index 00000000..3697fe2e
--- /dev/null
+++ b/app/src/main/res/values-hu/strings.xml
@@ -0,0 +1,440 @@
+
+
+
+ Verzió %s
+ Beállítások
+
+
+ %s növények
+
+ Összes növény
+
+ Összes
+ Tevékenység hozzáadva
+ Jegyzet hozzáadva
+ Öntözés hozzáadva
+ Kert törölve
+
+ Biztos hogy törlöd a <b>\"%s\"</b> kertet? Ezáltal a növények nem törlődnek.
+ Víz
+ Tevékenység
+
+ A kisalkalmazás nem elérhető, ha a titkosítás engedélyezve van
+ Új növény hozzáadása
+ Növény részletek
+ Növény statisztikák
+
+ Új kép kamerával
+
+ Választás galériából
+ Válassz egy lehetőséget
+ Válassz egy képet
+ Alkalmazás másik növényre
+ Növény kiválasztása
+ Kép hozzáadva
+ Másik választása
+
+ Ütemterv
+ Ütemterv részletei
+ Ütemterv dátuma
+ Név
+ Gyenge táplálás
+ Leírás
+
+ Gyenge táplálás 2 hét öblítéssel
+
+ + Új táplálás
+ + Új ütemterv
+ + Új adalékanyag
+ Adalékanyagok
+ Adalékanyag
+ Dátumtól
+ Szakasztól
+ Dátumig
+ Szakaszig
+ Megosztás az ütemtervből
+ Megosztás az előzőből
+ Biztos vagy benne?
+ Másoljam az ütemtervet?
+ Töröljem a kiválasztott ütemtervet?
+ Ütemterv másolva
+ Ütemterv törölve
+ Visszavonás
+ Táplálási ütemtervek
+ Táplálási ütemterv
+ Alkalmazás frissítve
+ Alkalmazás frissítve a %s verzióra
+ Változások megtekintése
+ Bezárás
+
+
+ Öntözés %s
+ Több növény öntözése
+
+ Jegyzetek
+ Víz részletei
+ Víz pH
+ PPM
+ Túlfolyás
+ Mennyiség (%s)
+ Hőmérséklet (°%s)
+ Dátum és idő
+ Mai dátum
+ Dátum
+ Mai nap
+ Érlelve %s napig
+
+ Öntözések
+ Szakasz változások
+
+ Duplikálás
+ Másolás máshova
+ Tevékenység szerkesztése
+ Tevékenység törlése
+
+ Növények kijelölése
+ Kertek
+ Összes növény
+ + Új kert
+ Továbbiak
+ Beállítások
+ Törlés
+ Megosztás
+ Fénykép
+ Legutóbbi öntözés
+ Öntözés újra
+
+ Növény neve
+ Új növény
+ Növény fajtája
+ Lemon haze
+ Termesztés részletei
+ Ültetés dátuma
+ Termőközeg
+
+ Termőközeg részletei
+
+ Föld, Perlit 50/50 keveréke
+ Klónból?
+ Szakasz részletei
+ Növény szakasza
+ Fényképek megtekintése
+ Napló megtekintése
+ Statisztika megtekintése
+ Fényképek
+ Napló
+ Statisztikák
+ Szűrés
+ Elültetve
+ Csírázás
+ Dugvány
+ Vegetáció
+ Virágzás kezdete
+ Virágzás
+ Virágzat beérése
+ Szárítás
+ Érlelés
+ Learatva
+
+ Learatva %s napja
+ Kert exportálása
+ Kert módosítása
+ Kert törlése
+ Exportálás
+
+ Teljes időtartam:
+ Összes öntözés:
+ Összes öblítés:
+ Összes öntözés:
+ Átlag felhasznált öntözővíz:
+ Átlag idő két öntözés között:
+ Átlag %s öntözővíz:
+ Adalékanyagok szűrése
+ pH
+ TDS
+
+ Min:
+ Max:
+ Átlag:
+ Hőmérséklet
+ Általános
+ Nem sikerült a növény betöltése
+ FIM
+ Öblítés
+ Levéltrágyázás
+ Low Stress Training
+ Nyalóka
+ Kártevőirtás
+ Toppolva
+ Átültetve
+ Ritkítás
+ ScrOG lomb igazítás
+ Supercrop
+ Monstercrop
+
+ Öntözve
+ Jegyzet
+ Esemény törlése?
+ Biztos hogy töröljem?
+ Biztos hogy törlöd a <b>%s</b> képeket? A törlés végleges!
+ Módosítás
+ szakasz
+ Beállít
+ Módosít
+ Hozzáad
+ Mégsem
+ tevékenység
+ OK
+ A mező kötelező
+ Hiba - helytelen jelszó
+ Öntözés módosítása
+ hozzáadva
+ Tevékenység duplikálva
+ több növény
+ Tevékenység hozzáadva
+ Jegyzet frissítve
+ Frissítve
+ Szakasz frissítve
+ Esemény törölve
+ Kész
+ Kert
+ Mindegyik kijelölése
+ Kijelölések törlése
+ Elfogadás
+ Engedélyezés
+ Tiltás
+ Magonc:
+ Magonc
+
+ A francba
+ Úgy tűnik az alkalmazás a legutóbbi használatkor összeomlott. Szeretnéd elküldeni az anonim jelentést? Ezek ide lesznek elküldve: <a href=\"https://github.com/7LPdWcaW/GrowTracker-Android/issues\">github.com/7LPdWcaW/GrowTracker-Android/issues</a>, személyes információt nem tartalmaznak. Opcionálisan posztolhatod is a reportokat kézileg, ha szeretnéd ide: <a href=\"https://reddit.com/r/growutils\">reddit.com/r/growutils</a>. A jelentések itt vannak tárolva: <i>%s<i>
+ Igen
+ Nem
+
+ Add meg a jelszavad
+ Adj meg egy jelszót
+ Add meg újra a jelszavad
+ Hiba - a jelszavaid nem egyeznek
+
+ Egy súlyos probléma lépett fel a kerted mentése közben, kérlek készíts biztonsági mentést ezekről az adatokról
+ Elhasznált
+ %s -el ezelőtt
+ Öntözve <b>%s</b> ideje
+ Utoljára öntözve <b>%s</b> ideje
+ Elültetve <b>%s</b> ideje
+
+ A külső tárolóhoz való hozzáférés szükséges hogy ezen alkamazás fényképeket tudjon tárolni. További adatot nem használ az alkalmazás.
+
+ Öntözések hozzáadva
+ Törölni szeretnéd a következőt: <b>%s</b> és minden képet ami hozzá tartozik. Biztos vagy benne? A törlés végleges!
+ Növény törlése...
+
+ Növény megkettőzve
+ Megnyitás
+
+ A növekedési napló exportálása....
+ A kert növekedési naplójának exportálása....
+ %s hozzáadva
+ A név nem lehet üres
+ Exportálás %s
+
+ Virágföld
+ Hirdopónia
+ Kókuszrost
+ Aeropónia
+ Képek elrejtése
+ Képek mutatása
+
+ Az alapértelmezett kert mutatása indításkor, jelenleg: <b>%s</b>
+ Válassz megjelenítési stílust, jelenleg: %s
+ Alap mértékegység kiválasztása, jelenleg: <b>%s</b>
+ Adalékanyag mértékegység kiválasztása, jelenleg: <b>%s</b>
+ Alap hőmérséklet típus kiválasztása, jelenleg: <b>%s</b>
+ Jelenleg: <b>%s MiB</b> / Használatban <b>%s</b>
+ A titkosítás feloldásához ezt a jelszót használd, hogy az adatok ne töltődjenek be
+ Figyelmeztetés
+ Ez egy alap formája az AES titkosításnak. Ez nem garantál semmilyen védelmet a hatósági szervektől.
+ Biztosnági mentés engedélyezve, a mentések ide lesznek tárolva: %s
+ Visszaállítás a következő állapotra: %s kész
+ Nem sikerült a mentás visszaállítása az alábbi helyről: %s. A fájl lehet hogy %s.
+ titkosítva
+ titkosítatlan
+
+ Válassz mértékegységet
+ Válassz hőmérsékletet
+ Válassz kertet
+ Válassz mentést
+ Titkosított módot kellett volna választanod ugyanezzel a jelszóval hogy ezt a mentést visszaállítsd.
+
+ Elmentve ide
+ Nincs mentés a visszaállításhoz
+ Összes/Semelyik
+
+ Beviteli pH
+ Túlfolyó pH
+ Átlag pH
+ Átlag hőmérséklet
+ Átlag túlfolyó pH
+ Átlag %s
+
+ Növény képek
+
+ Beviteli pH:
+ Kimeneti pH:
+ Mennyiség:
+ Hőmérséklet:
+ Adalékanyagok:
+ EC:
+ PPM:
+
+ Ütemterv kijelölése
+ Tevékenységek
+ Sötét mód
+ Sötét mód erőltetése a rendszerbeállításoktól függetlenül
+ Learatottak elrejtése
+ Minden learatott növény elrejtése
+ Növények sorrendjének megfordítása
+ Fordított sorrendben mutatja a növényeket. (Újraindítás szükséges)
+ Alapértelmezett kert
+
+ Mértékegységek
+ Szállítási egység
+ Mértékegység
+ Hőmérséklet egység
+ TDS érték
+ Alapértelmezett TDS egység, jelenleg: <b>%s</b>
+ Adatok kezelése
+ Automatikus mentés
+ Automatikus mentés 24 óránként
+ Mentés most
+ Mentés maximális mérete
+ Méret MiB-ban
+ Mentés hatáértéke (MiB)
+ Visszaállítás mentésből
+ Adatok titkosítása
+ Egy PIN-t ad az alkamazáshoz és titkosít minden adatot/képet
+ Biztosíték
+ Biztosíték jelszó megadása az adatok hozzáféréséhez
+ Bővítmények
+ Az alkalmazásról
+ Olvass el
+ Adatok exportálása
+ Kártya stílusa
+ Nincs mutatható növény
+ Nincs ütemterv
+ Nincs fénykép
+ Előző tevékenységek
+ Naptár mutatása/elrejtése
+
+ %d kijelölve
+ Köszönjük az alábbiakat
+ Fordította
+ Segíts a fordításban
+ Adatok és képek titkosítása, ez eltarthat egy darabig...
+ Adatok és képek titkosításának feloldása, ez eltarthat egy darabig...
+
+
+ 1s
+ mp
+ p
+ ó
+ nap
+ hét
+ hónap
+ év
+
+ Részletek
+
+
+ - másodperc
+ - másodpercek
+
+
+
+ - perc
+ - percek
+
+
+
+ - óra
+ - órák
+
+
+
+ - nap
+ - napok
+
+
+
+ - hét
+ - hetek
+
+
+
+ - hónap
+ - hónapok
+
+
+
+ - év
+ - évek
+
+
+
+ - Eredeti
+ - Kompakt
+ - Extrém
+
+
+ Minden nem mentett módosítás elveszik
+ Kilépés
+ Növények
+ Páratartalom
+ Jelenlegi hőmérséklet:
+ Jelenlegi páratartalom:
+ Világítás ütemterv
+ Elem törlése?
+ Biztos hogy törlöd: <b>%s</b>?
+ Mutatás
+ Elrejtés
+ Lámpa BE
+ Lámpa KI
+
+ Utolsó mentés: <b>%s</b>
+ Képek mentési helye, jelenleg <b>%s</b>
+ Nem sikerült a képek mentési helyét megadni
+ Képek mentési helye
+ Kép gyorsítótár törölve
+ Képfájl és memória gyorsítótár törölve
+ Képek importálása, ez eltarthat egy darabig...
+ Feladat időzítése
+ Kép importálás kész
+ Kép visszafejtés kész
+ Kép titkosítás kész
+ Nincs mutatható tevékenység
+
+ Termesztési napló exportálása a következőnek: %s
+ A %s exportálása sikeres
+ Exportálva innen: %s ide %s
+ Exportálás sikeres
+ Képek csatolása?
+ Nincs adat
+
+ Fényképek készítéséhez szükséges a kamera engedélyezése
+ Mentések kezelése
+ Mentés helye
+ Mentés helye, jelenleg <b>%s</b>
+ Nem sikerült a mentés helyének beállítása
+ Nem sikerült a növények importálása fájlból
+ Növények importálása fájlból
+ Növények sikeres importálása fájlból
+
+ %s statisztika
+ Átlagos mennyiség táplálásonként:
+ Ennyi alkalommal használva:
+ Összes adalékanyag:
+
+ Adalékanyagok koncentrációja
+ Adalékanyagok az idő során
+ Adalékanyagok eloszlása
+
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
new file mode 100644
index 00000000..7ee3ec09
--- /dev/null
+++ b/app/src/main/res/values-nb/strings.xml
@@ -0,0 +1,268 @@
+
+
+
+ Versjon %s
+ Innstillinger
+
+
+ %s planter
+
+ Alle planter
+
+ Alle
+ La til handlinger
+ La til notater
+ La til vanning
+ Slettet hagen
+
+ Er du sikker på at du vil slette hagen <b>\"%s\"</b>? Dette vil ikke slette plantene.
+ Vann
+ Handling
+
+ Widget er ikke tilgjengelig når kryptering er slått på
+ Legg til ny plante
+ Plantedetaljer
+ Plantestatistikker
+
+ Fra kamera
+
+ Fra galleri
+ Velg et alternativ
+ Velg bilde
+ Bruk på en annen plante
+ Velg plante
+ La til bilde
+ Ta et til
+
+ Tidsplaner
+ Tidsplandetaljer
+ Tidsplan-dato
+ Navn
+ Lett mating
+ Beskrivelse
+
+ Lett mating med 2 ukers flush
+
+ + Ny mating
+ + Ny tidsplan
+ + Ny additiv
+ Additiver
+ Additiv
+ Fra dato
+ Fra fase
+ Til dato
+ Til fase
+ Fyll ut ved hjelp av tidsplan
+ Fyll ut ved hjelp av forrige
+ Er du sikker?
+ Kopiere valgte tidsplan?
+ Slette valgt tidsplan?
+ Tidsplan kopiert
+ Tidsplan slettet
+ Angre
+ Tidsplaner for mating
+ Tidsplan for mating
+ App er oppdatert
+ Appen er blitt oppdatert til versjon %s
+ Se endringer
+ Avvis
+
+
+ Vanning %s
+ Vanning av flere planer
+
+ Notater
+ Vanndetaljer
+ Vann-pH
+ PPM
+ Dato og tid
+ Nå
+ Dato
+ I dag
+ Vanninger
+ Dupliser
+ Kopier til
+ Endre handling
+ Slett handling
+
+ Velg planter
+ Hager
+ Alle planter
+ + Ny hage
+ Innstillinger
+ Slett
+ Del
+ Bilde
+ Siste vanning
+ Vann igjen
+
+ Plantenavn
+ Ny plante
+ Voksedetaljer
+ Plantedato
+ Se bilder
+ Se historikk
+ Se statistikker
+ Bilder
+ Historikk
+ Statistikker
+ Filter
+ Plantet
+ Eksporter hage
+ Rediger hage
+ Slett hage
+ Eksporter
+
+ Min:
+ Max:
+ Gj.snitt:
+ Temperatur
+ Generelt
+ Kunne ikke laste plante
+ FIM
+ Slette denne hendelser?
+ Er du sikker på at du vil slette
+ Er du sikker på at du vil slette <b>%s</b> bilder? Du vil ikke kunne gjenopprette de.
+ Rediger
+ Angi
+ Endre
+ Legg til
+ Avbryt
+ handling
+ OK
+ Feltet er obligatorisk
+ Feil - Ukorrekt passord
+ Rediger vanning
+ lagt til
+ Dupliserte handlingen
+ flere planter
+ La handlingen til
+ Notat oppdatert
+ oppdatert
+ Ferdig
+ Hage
+ Velg alle
+ Velg ingen
+ Aksepter
+ Aktiver
+ Avslå
+ Oisann.
+ Ser ut som at det var en stopp i programmet sist gang du brukte appen. Vil du sende disse anonyme rapportene? Rapportene vil sendes til <a href=\"https://github.com/7LPdWcaW/GrowTracker-Android/issues\">github.com/7LPdWcaW/GrowTracker-Android/issues</a>, ingen personlig informasjon vil inkluderes. Du kan selv publisere disse rapportene på <a href=\"https://reddit.com/r/growutils\">reddit.com/r/growutils</a> manuelt hvis du vil. Rapporter lagres i <i>%s<i>
+ Ja
+ Nei
+
+ Angi passordet ditt
+ Angi et passord
+ Sletter plante...
+
+ Dupliserte plante
+ Åpne
+
+ La til %s
+ Navn kan ikke stå tomt
+ Eksporterer %s
+
+ kryptert
+ ukryptert
+
+ Handlinger
+ Tving mørkt tema
+ Tvinger appen til å bruke det mørke temaet, uavhengig av systemets innstillinger.
+ Enheter
+ Måleenhet
+ Temperaturenhet
+ Automatisk sikkerhetskopi
+ Sikkerhetskopier plantedataene dine hver 24 time
+ Sikkerhetskopier nå
+ Sikkerhetskopi-størrelsesbegrensning
+ Størrelse i MiB
+ Sikkerhetskopi-begrensning (MiB)
+ Gjenopprett fra sikkerhetskopi
+ Krypter data
+ En kodelås legges til appen og krypterer alt av data og bilder
+ Tillegg
+ Om
+ Les meg
+ Eksporter data
+ Kortstil
+ Det er ingen planter å vise
+ Det er ingen tidsplaner
+ Det er ingen bilder å vise
+ Tidligere handlinger
+ Vis/skjul kalender
+
+ %d valgt
+ Tusen takk til de følgende
+ Oversettelser av
+ Hjelp til med å oversette
+ Krypterer data og bilder. Dette kan ta en stund...
+ Dekrypterer data og bilder. Dette kan ta en stund...
+
+
+ 1s
+ s
+ m
+ t
+ d
+ u
+ m
+ å
+
+ Detaljer
+
+
+ - sekund
+ - sekunder
+
+
+
+ - minutt
+ - minutter
+
+
+
+ - time
+ - timer
+
+
+
+ - dag
+ - dager
+
+
+
+ - uke
+ - uker
+
+
+
+ - måned
+ - måneder
+
+
+
+ - år
+ - år
+
+
+
+ - Original
+ - Kompakt
+ - Ekstrem
+
+
+ Du vil miste ulagrede endringer
+ Gå ut
+ Planter
+ Fuktighet
+ Nåværende temperatur:
+ Nåværende fuktighet:
+ Slette dette elementet
+ Er du sikker på at du vil slette <b>%s</b>?
+ Vis
+ Skjul
+ Inkludere bilder?
+ Det er ingen data tilgjengelig
+
+ Kameratillatelse trengs for å ta bilder
+
diff --git a/app/src/main/res/values-nl-rNL/strings.xml b/app/src/main/res/values-nl-rNL/strings.xml
index d8f79ab7..ba155989 100644
--- a/app/src/main/res/values-nl-rNL/strings.xml
+++ b/app/src/main/res/values-nl-rNL/strings.xml
@@ -129,7 +129,9 @@
Ontkiemd
Gewied
Groeiende
+ Ontkieming
Bloeiende
+ Rijping
Drogende
Genezende
Geplukt
@@ -143,9 +145,13 @@
Totaaltijd:
Totaal water gegeven:
Totaal bijgesteld:
+ Totaal water gegeven:
+ Gem. waterverbruik:
Gem. tijd tussen water geven:
+ Gemiddeld %swater:
Toevoegingen filteren
pH-waarde
+ TDS
Min.:
Max.:
@@ -153,7 +159,7 @@
Temperatuur
Algemeen
Kan plant niet laden
- Oeps, vergeten! (OV)
+ FIM
Bijstellen
Bladvoeding
Lagestresstraining
@@ -163,6 +169,8 @@
Verpot
Bijsnijden
ScrOG Tuck
+ Supergroot gewas
+ Immens gewas
Water gegeven
Aantekening
@@ -265,6 +273,9 @@
Toevoer-pH-waarde
Afvoer-pH-waarde
Gemiddelde pH-waarde
+ Gemiddelde temperatuur
+ Gemiddelde afvoer-pH-waarde
+ Gemiddeld %s
Plantenfoto\'s
@@ -397,7 +408,9 @@
Afbeeldings- en geheugencache gewist
Bezig met importeren; even geduld...
Gegevenstaak
- Taak volbracht
+ De afbeelding is geïmporteerd
+ De afbeelding is ontsleuteld
+ De afbeelding is versleuteld
Er zijn geen acties
Bezig met exporteren van groeilog van %s...
@@ -415,4 +428,13 @@
Kan planten niet importeren uit bestand
Planten importeren uit bestand
De planten zijn geïmporteerd
+
+ %s-statistieken
+ Gem. per voeding:
+ Aantal keer gebruikt:
+ Totale toevoegingen:
+
+ Toegevoegde concentratie
+ Toevoegingen in de loop der tijd
+ Toegevoegde verspreiding
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 0be65b56..b67d79f0 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -129,7 +129,9 @@
Прорастание
Срез
Вегетация
+ Формирование соцветий
Цветение
+ Созревание
Сушка
Выдержка (пролечка)
Собрано
@@ -143,9 +145,13 @@
Общее время:
Всего поливов:
Всего промываний:
+ Всего воды:
+ Средний объём полива:
Средний интервал между поливами:
+ Сред. полив %s:
Фильтр удобрений
pH
+ Минерализация
Мин:
Макс:
@@ -153,7 +159,7 @@
Температура
Общие
Не удалось загрузить растение
- Fuck I Missed (FIM)
+ FIM
Промывание
Листовая подкормка
LST (низкострессовая тренировка)
@@ -163,6 +169,8 @@
Пересадка
Тримминг
Применение ScrOG
+ Суперкроппинг
+ Монстркроппинг
Полито
Заметка
@@ -209,7 +217,7 @@
Ошибка - пароли не совпадают
Во время сохранения данных произошла ошибка. Пожалуйста, создайте резервную копию данных.
- Готово
+ Сделано
%s назад
Полито <b>%s</b> назад
Последний полив <b>%s</b> назад
@@ -265,6 +273,9 @@
Входной pH
pH стока
Средний pH
+ Средняя температура
+ Средний pH стоков
+ Средний %s
Фото растения
@@ -324,12 +335,12 @@
1с
- с
+ сек
мин
- ч
- д
- н
- м
+ час
+ дн
+ нед
+ мес
г
Детали
@@ -411,7 +422,9 @@
Кеш изображений очищен
Импорт изображений, может занять некоторое время...
Обработка данных
- Задание выполнено
+ Импорт изображений завершён
+ Расшифровка изображений завершена
+ Шифрование изображений завершено
Нет действий
Экспортирую журнал для %s
@@ -429,4 +442,13 @@
Не удалось импортировать растения
Импортировать растения из файла
Растение успешно импортировано из файла
+
+ Статистика %s
+ Средняя доза:
+ Использовано раз:
+ Всего удобрений:
+
+ Концентрация удобрений:
+ Удобрений со временем
+ Распределение удобрений:
diff --git a/app/src/main/res/values-sl-rSI/strings.xml b/app/src/main/res/values-sl-rSI/strings.xml
new file mode 100644
index 00000000..bb77e8d5
--- /dev/null
+++ b/app/src/main/res/values-sl-rSI/strings.xml
@@ -0,0 +1,455 @@
+
+
+
+ Različica %s
+ Nastavitve
+
+
+ %srastlin
+
+ Vse rastline
+
+ Vse
+ Dejanja dodana
+ Zapiski dodani
+ Zalivanje dodano
+ Vrt izbrisan
+
+ Ali ste prepričani, da želite izbrisati vrt <b>%s</b>? Dejanje ne bo izbrisalo rastlin
+ Voda
+ Dejanje
+
+ Pripomoček ni na voljo, ko je vključena nastavitev za šifriranje
+ Dodaj novo rastlino
+ Podrobnosti o rastlini
+ Statistika o rastlini
+
+ Iz fotoaparata
+
+ Iz galerije
+ Izberi možnost
+ izberi sliko
+ Uporabi za drugo rastlino
+ Izberi rastlino
+ Slika dodana
+ Zajami še eno
+
+ Urniki
+ Podrobnosti o urniku
+ Datum urnika
+ Ime
+ Rahlo hranjenje
+ Opis
+
+ Rahlo hranjene z 2 tedenskim izpiranjem
+
+ + Novo hranjenje
+ + Nov urnik
+ + Nov dodatek
+ Dodatki
+ Dodatek
+ Od datuma
+ Od faze
+ Do datuma
+ Do faze
+ Napolni iz urnika
+ Napolni iz prejšnjega
+ Ali ste prepričani?
+ Kopiranje izbranega urnika?
+ Brisanje izbranega urnika?
+ Urnik kopiran
+ Urnik izbrisan
+ Razveljavi
+ Urniki hranjenja
+ Urniki hranjenja
+ Aplikacija posodobljena
+ Aplikacija je bila posodobljena na različico %s
+ Preglej spremembe
+ Zavrzi
+
+
+ Zalivanje %s
+ Zalivanje več rastlin
+
+ Beležka
+ Podrobnosti o vodi
+ pH vode
+ ppm
+ Odtok
+ Količina (%s)
+ Temp. (º%s)
+ Datum in čas
+ Zdaj
+ Datum
+ Danes
+ Zori že %s dni
+
+ Zalivanja
+ Spremembe faz
+
+ Podvojeno
+ Kopiraj v
+ Uredi dejanje
+ Izbriši dejanje
+
+ Izberi rastline
+ Vrtovi
+ Vse rastline
+ + Nov vrt
+ Dodatno
+ Nastavitve
+ Izbriši
+ Deli
+ Slika
+ Zadnje zalivanje
+ Zalij znova
+
+ Ime rastline
+ Nova rastlina
+ Sorta rastline
+ Lemon haze
+ Podrobnosti gojenja
+ Datum posaditve
+ Substrat
+
+ Podrobnosti substrata
+
+ Zemlja, Perlit, 50/50 mešanica
+ Iz potaknjenca?
+ Podrobnosti faze
+ Faza rastline
+ Preglej slike
+ Preglej zgodovino
+ Preglej statistiko
+ Slike
+ Zgodovina
+ Statistika
+ Filter
+ Posajeno
+ Kalitev
+ Potaknjenec
+ Vegetacija
+ Brstenje
+ Cvetenje
+ Zorenje
+ Sušenje
+ Zorenje
+ Požeto
+
+ Požeto pred %s
+ Izvozi vrt
+ Uredi vrt
+ Izbriši vrt
+ Izvozi
+
+ Celoten čas:
+ Skupno zalivanja:
+ Skupno izpiranja:
+ Skupno vode:
+ Uporabljeno povp. vode:
+ Povp. čas med zalivanji:
+ povp. %s vode:
+ Dodatki filtra
+ pH
+ TDS
+
+ Min:
+ Max:
+ Povp.:
+ Temperatura
+ Splošno
+ Neuspešno nalaganje rastline
+ FIM
+ Izpiranje
+ Foliarno hranjenje
+ Low Stress Training
+ Lizika
+ Aplikacija pesticida
+ Vršičkano
+ Presajeno
+ Obrezano
+ ScrOG tlačenje
+ Supercrop
+ Monstercrop
+
+ Zalito
+ Zapisek
+ Izbriši ta dogodek?
+ Ali ste prepričani, da želite izbrisati?
+ Ali ste prepričani, da želite izbrisati <b>%s</b> slike ? Teh ne boste mogli več obnoviti.
+ Uredi
+ Faza
+ Nastavi
+ Spremeni
+ Dodaj
+ Prekliči
+ Dejanje
+ Ok
+ Polje je potrebno
+ Napaka - nepravilno geslo
+ Uredi zalivanje
+ Dodano
+ Podvojeno dejanje
+ Več rastlin
+ Dejanje dodano k
+ Zapisek posodobljen
+ Posodobljeno
+ Faza posodobljena
+ Dogodek izbrisan
+ Končano
+ Vrt
+ Izberi vse
+ Ne izberi nobenega
+ Sprejmi
+ Omogoči
+ Zavrni
+ Sadika
+ Sadika
+
+ Oh ne
+ Izgleda, da je od zadnje uporabe aplikacije prišlo do sesutja.
+Ali bi želeli poslati ta anonimna poročila? Ta poročila bodo poslana na <a href=\"https://github.com/7LPdWcaW/GrowTracker-Android/issues\">github.com/7LPdWcaW/GrowTracker-Android/issues</a>, priloženih ne bo nič osebnih podatkov. Poljubno lahko tudi objavite ta poročila na <a href=\"https://reddit.com/r/growutils\">reddit.com/r/growutils</a> ročno, če želite. Poročila so shranjena v <i>%s<i>
+ Da
+ Ne
+
+ Vnesite svoje geslo
+ Vnesite geslo
+ Znova vnesite svoje geslo
+ Napaka - geslo se ne ujema
+
+ Nastala je usodna napaka pri shranjevanju podatkov o vrtu, prosimo naredite varnostno kopijo teh podatkov
+ Vzeto
+ Pred %s
+ Zalito pred <b>%s</b>
+ Zadnje zalivanje pred <b>%s</b>
+ Posajeno pred <b>%s</b>
+
+ Za shranjevanje fotografij je potreben dostop do zunanje shrambe. Nobeni drugi podatki niso potrebni.
+
+ Zalivanja dodana
+ Izbrisali boste <b>%s</b> in vse slike povezane s tem, ste prepričani ? To ne more biti razveljavljeno.
+ Brisanje rastline...
+
+ Podvojena rastlina
+ Odpri
+
+ Izvažanje dnevnika gojenja...
+ Izvažanje dnevnika gojenja vrta...
+ %s dodano
+ Ime ne more biti prazno
+ Izvažanje %s
+
+ Zemlja
+ Hidroponika
+ Substrat iz kokosa
+ Aeroponika
+ Skrij slike
+ Prikaži slike
+
+ Privzet vrt za prikaz ob odprtju, trenutno: <b>%s</b>
+ Izberi stil kartic, trenutno: %s
+ Uporaba privzetih enot merjenja, trenutno: <b>%s</b>
+ Uporaba privzetih enot merjenja dodatkov, trenutno: <b>%s</b>
+ Uporaba privzetih enot merjenja temperature, trenutno: <b>%s</b>
+ Trenutno: <b>%s MiB</b> / Uporabljate <b>%s</b>
+ Vnesite to geslo med fazo dešifriranja, da preprečite nalaganje podatkov
+ Opozorilo
+ To je osnovna oblika AES šifriranja, ki temelji na podanem geslu. To ni garantirana oblika zaščite pred organi pregona.
+ Varnostna kopija omogočena, kopije bodo shranjene v %s
+ Obnovitev v %s končana
+ Obnovitev varnostne kopije iz %s je spodletela. Datoteka je morda %s
+ Šifrirano
+ Nešifrirano
+
+ Izberi mersko enoto
+ Izberi temperaturo
+ Izberi vrt
+ Izberi varnostno kopijo
+ Da obnovite to varnostno kopijo, morate imeti omogočen način šifracije z enakim geslom
+
+ Kopirano do
+ Ni varnostnih kopij iz katerih bi lahko obnovili podatke
+ Vse/Nič
+
+ Vnesi pH
+ pH odtočne vode
+ Povprečen pH
+ Povprečna temp
+ Povprečen odtok pH
+ Povprečno %s
+
+ Slike rastline
+
+ Vhodni pH:
+ Izhodni pH:
+ Količina:
+ Temp.:
+ Dodatki:
+ EC:
+ ppm:
+
+ Izberi urnik
+ Dejanja
+ Vsili temno temo
+ Vsili aplikaciji, da uporabi temno temo, ne glede na sistemske nastavitve dnevne-temne teme
+ Skrij požeto
+ Skrij vse požete rastline
+ Obrni vrstni red rastlin
+ Pokaže rastline v obratnem vrstnem redu. (Potreben je ponovni zagon aplikacije)
+ Privzet vrt
+
+ Enote
+ Enota dostave
+ Merska enota
+ Temperaturska enota
+ TDS enota
+ Privzeta uporaba TDS, trenutno: <b>%s</b>
+ Upravljanje podatkov
+ Samodejna varnostna kopija
+ Samodejno naredi varnostno kopijo vsakih 24 ur
+ Naredi varnostno kopijo zdaj
+ Omejitev velikosti varnostne kopije
+ Velikost v MiB
+ Omejena velikost varnostne kopije (MiB)
+ Obnovi iz varnostne kopije
+ Šifriraj podatke
+ Doda zaklepanje aplikacije s pin kodo in zašifrira vse podatke/slike
+ Varovalni mehanizem
+ Nastavi geslo varnostnega mehanizma da preprečiš dostop do podatkov
+ Dodatki
+ O aplikaciji
+ Preberi
+ Izvozi podatke
+ Stil kartic
+ Ni rastlin za prikaz
+ Ni urnikov
+ Ni slik za prikaz
+ Pretekla dejanja
+ Prikaži/skrij kolendar
+
+ %d izbrano
+ Hvala vam do slednjega
+ Prevodi
+ Pomagaj pri prevodu
+ Šifriranje podatkov in slik
+ Dešifriram podatke in slike, to lahko vzame nekaj časa...
+
+
+ 1 sekunda
+ s
+ min.
+ h
+ dni
+ ted.
+ mes.
+ l
+
+ Podrobnosti
+
+
+ - sekunda
+ - sekundi
+ - sekund
+ - sekunde
+
+
+
+ - minuta
+ - minuti
+ - minut
+ - minute
+
+
+
+ - ura
+ - uri
+ - ur
+ - ure
+
+
+
+ - dan
+ - dneva
+ - dni
+ - dnevi
+
+
+
+ - teden
+ - tedna
+ - tednov
+ - tedni
+
+
+
+ - mesec
+ - meseca
+ - mesecev
+ - meseci
+
+
+
+ - leto
+ - leti
+ - let
+ - leta
+
+
+
+ - Izvirno
+ - Kompaktno
+ - Ekstremno
+
+
+ Izgubili boste kakršnekoli neshranjene spremembe
+ Končaj
+ Rastline
+ Vlažnost
+ Trenutna temp.:
+ Trenutna vlažnost:
+ Urnik osvetljave
+ Izbriši ta predmet?
+ Ali ste prepričani da želite izbrisati <b>%s</b>?
+ Pokaži
+ Skrij
+ Vključene luči
+ Izključene luči
+
+ Zadnja varnostna kopija: <b>%s</b>
+ Mesto shrambe za slike, trenutno <b>%s</b>
+ Neuspešna nastavitev mesta slike
+ Mesto shrambe slik
+ Počisti predpomnilnik slik
+ Shramba slik in spomin prepomnilnika počiščena
+ Uvažanje slik, to lahko traja nekaj časa...
+ Opravilo podatkov
+ Uvoz slike končan
+ Dešifriranje slike končano
+ Šifriranje slike končano
+ Ni dejanj za prikaz
+
+ Izvažanje dnevnika gojenja za %s
+ Izvoz %s končan
+ Izvoženo %s v %s
+ Izvoz končan
+ Vključim slike?
+ Podatki niso na voljo
+
+ Za zajem slik je potreben dostop do fotoaparata
+ Uprabljanje varnostnih kopij
+ Mesto shrambe varnostnih kopij
+ Mesto shrambe varnostnih kopij, trenutno <b>%s</b>
+ Neuspešna nastavitev mesta varnostne kopije
+ Neuspešen uvoz rastlin iz datoteke
+ Uvozi rastline iz datoteke
+ Uspešen uvoz rastlin iz datoteke
+
+ %s statistika
+ povp. na dovajanje:
+ Kolikokrat uporabljeno:
+ Skupno aditivov:
+
+ Koncentracija aditivov
+ Aditivi čez čas
+ Distribucija aditivov
+
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index b6e3bfc0..abbd4730 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -129,7 +129,9 @@
Проростання
Зріз
Вегетація
+ Утворення суцвіть
Цвітіння
+ Дозрівання
Сушіння
Витримка (пролічка)
Зібрано
@@ -143,9 +145,13 @@
Загальний час:
Всього поливів:
Всього промивань:
+ Всього води:
+ Середній об\'єм поливу:
Середній час між поливами:
+ Середній полив %s:
Фільтр добрив
pH
+ Мінералізація
Мін:
Макс:
@@ -153,7 +159,7 @@
Температура
Загальні
Не вдалося завантажити рослину
- Fuck I Missed (FIM)
+ FIM
Промивання
Листкове підживлення
LST (низькостресове тренування)
@@ -163,6 +169,8 @@
Пересадка
Трімінг
Виконання ScrOG
+ Суперкропінг
+ Монстркропінг
Полито
Нотатка
@@ -209,7 +217,7 @@
Помилка - пароль не співпадає
Під час збереження саду сталася помилка, будь ласка створіть резервну копію цих даних
- Готово
+ Зроблено
%s тому
Полито <b>%s</b> тому
Востаннє полито <b>%s</b> тому
@@ -265,6 +273,9 @@
Вхідний pH
pH стоків
Середній pH
+ Середня температура:
+ Середній pH стоків
+ Середній %s
Світлини рослини
@@ -324,12 +335,12 @@
1с
- с
+ сек
хв
- г
- д
- т
- м
+ год
+ дн
+ тиж
+ міс
р
Деталі
@@ -411,7 +422,9 @@
Кеш зображень очищено
Імпортую зображення, це може тривати довго...
Обробка даних
- Завдання виконано
+ Імпорт зображень завершено
+ Розшифрування зображень завершено
+ Шифрування зображень завершено
Немає дій
Експортую журнал для %s
@@ -429,4 +442,13 @@
Не вдалося імпортувати
Імпортувати рослини з файлу
Імпорт завершено
+
+ Статистика %s
+ Середня доза:
+ Використано разів:
+ Всього добрив:
+
+ Концентрація добрив
+ Добрива з часом
+ Розподіл добрив
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 00000000..cb7ac2e4
--- /dev/null
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,79 @@
+
+
+
+ 版本 %s
+ 設定
+
+
+ 所有植物
+
+ 全部
+ 筆記已加入
+ 已刪除園子
+
+ 水
+ 增加新植物
+ 植物詳情
+
+ 從相機
+
+ 從圖庫
+ 選擇圖片
+ 選擇植物
+ 已加入圖片
+ 名字
+ 描述
+ 你確定嗎?
+ 筆記
+ 日期與時間
+ 今天
+ 複製到
+ 所有植物
+ 刪除
+ 分享
+ 相片
+ 查看歷史紀錄
+ 查看統計資料
+ 相片
+ 歷程
+ 統計
+ 過濾器
+ 筆記
+ 刪除這個活動?
+ 你確定要刪除
+ 編輯
+ 取消
+ 好
+ 全選
+ 好
+ 不
+
+ 輸入你的密碼
+ 輸入一組密碼
+ 重新輸入你的密碼
+ %s 之前
+ 正在刪除植物...
+
+ 打開
+
+ 顯示圖片
+
+ 警告
+ 已加密
+ 未加密
+
+ 選擇溫度
+ 關於
+
+ %d 已選擇
+ 幫助翻譯
+ 細節
+
+ 離開
+ 顯示
+ 隱藏
+ 包含圖片?
+ 沒有資料
+
+ 拍照需要相機權限
+
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 69471845..40be6fff 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -14,12 +14,11 @@
- #469990
- #e6beff
- #9A6324
- - #fffac8
- #800000
- #aaffc3
- #808000
- #ffd8b1
- - #000075
+ - #2222A5
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 73bfbd84..7f98f8e2 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -2,6 +2,7 @@
8dp
+ 16dp
4dp
190dp
256dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 765f15b8..985b5a77 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -129,12 +129,14 @@
Germination
Cutting
Vegetation
+ Budding
Flowering
+ Ripening
Drying
Curing
- Harvested
+ Harvested/Culled
- Harvested %s ago
+ Harvested/Culled %s ago
Export garden
Edit garden
Delete garden
@@ -143,9 +145,13 @@
Total time:
Total waters:
Total flushes:
+ Total water:
+ Ave. water used:
Ave. time between water:
+ Ave. %s water:
Filter additives
pH
+ TDS
Min:
Max:
@@ -153,7 +159,7 @@
Temperature
General
Failed to load plant
- Fuck I Missed (FIM)
+ FIM
Flush
Foliar Feed
Low Stress Training
@@ -163,6 +169,8 @@
Transplanted
Trim
ScrOG Tuck
+ Supercrop
+ Monstercrop
Watered
Note
@@ -265,6 +273,9 @@
Input pH
Runoff pH
Average pH
+ Average temp
+ Average runoff pH
+ Average %s
Plant photos
@@ -415,7 +426,9 @@
Image disk and memory cache cleared
Importing images, this may take a while…
Data task
- Task completed
+ Image import completed
+ Image decryption completed
+ Image encryption completed
There are no actions to show
Exporting grow log for %s
@@ -433,4 +446,13 @@
Failed to import plants from file
Import plants from file
Plants successfully imported from file
+
+ %s stats
+ Ave. per feeding:
+ Times used:
+ Total additive:
+
+ Additive concentration
+ Additives over time
+ Additives distribution
diff --git a/build.gradle b/build.gradle
index 4475281b..49e5280b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,8 +9,8 @@ buildscript {
dependencies {
- classpath 'com.android.tools.build:gradle:3.4.2'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.41"
+ classpath 'com.android.tools.build:gradle:7.0.3'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/fastlane/metadata/android/en-GB/changelogs/2630.txt b/fastlane/metadata/android/en-GB/changelogs/2630.txt
new file mode 100644
index 00000000..31598f76
--- /dev/null
+++ b/fastlane/metadata/android/en-GB/changelogs/2630.txt
@@ -0,0 +1,8 @@
+NOTE: This version is built with the latest Android SDK, but is only targeted for api 28 (Android 9). Some functionality may be broken.
+
+- Fixes crash with accessibility conflict
+- Fixes issue with gallery folders showing up in photos
+- Refactors export diary functionality
+- Fixes issue with crash when adding a new plant
+- Fixes issue with deleting plant not working
+- Various tidyups
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index b4c566b9..6260ae92 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Fri Aug 09 12:48:32 BST 2019
+#Wed Feb 24 12:49:44 GMT 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip