diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a83e78b..2a779bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to TCGdex +# Contributing to OpenHealth First off, thanks for taking the time to contribute! ❤️ @@ -31,19 +31,22 @@ All types of contributions are encouraged and valued. See the [Table of Contents This project and everyone participating in it is governed by the [DZEIO Code of Conduct](https://github.com/dzeiocom/OpenHealth/blob/master/CODE_OF_CONDUCT.md). -By participating, you are expected to uphold this code. Please report unacceptable behavior to . +By participating, you are expected to uphold this code. Please report unacceptable behavior to . ## I Have a Question + +You ask them on the Github Repository, it is best to search for existing [Issues](https://github.com/dzeiocom/OpenHealth/issues) that might help you. +In case you have found a suitable issue and still need clarification, you can write your question in a new issue. It is also advisable to search the internet for answers first. If you then still feel the need to ask a question and need clarification, we recommend the following: - Open an [Issue](https://github.com/dzeiocom/OpenHealth/issues/new). - Provide as much context as you can about what you're running into. -- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. +- Provide project and platform versions (Android Version, Application Version), depending on what seems relevant. We will then take care of the issue as soon as possible. @@ -73,13 +76,12 @@ Depending on how large the project is, you may want to outsource the questioning A good issue report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. -- Determine if your issue is not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://www.tcgdex.dev). If you are looking for support, you might want to check [this section](#i-have-a-question)). -- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already an issue report existing for your bug or error in the [bug tracker](https://github.com/tcgdex/javascript-sdk/issues). +- Determine if your issue is not an error on your side e.g. using incompatible environment components/versions. If you are looking for support, you might want to check [this section](#i-have-a-question)). +- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already an issue report existing for your bug or error in the [bug tracker](https://github.com/dzeiocom/OpenHealth/issues). - Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue. - Collect information about the bug: - Stack trace (Traceback) - - OS, Platform and Version (Windows, Linux, macOS, x86, ARM) - - Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. + - OS, Platform and Version (Phone, OS Version, App Version, etc...) - Possibly your input and the output - Can you reliably reproduce the issue? And can you also reproduce it with older versions? @@ -87,16 +89,16 @@ A good issue report shouldn't leave others needing to chase you up for more info #### How Do I Submit a Good Bug Report? -> You must never report security related issues, vulnerabilities or bugs to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to . +> You must never report security related issues, vulnerabilities or bugs to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to . We use GitHub issues to track bugs and errors. If you run into an issue with the project: -- Open an [Issue](https://github.com/tcgdex/javascript-sdk/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) +- Open an [Issue](https://github.com/dzeiocom/OpenHealth/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) - Explain the behavior you would expect and the actual behavior. - Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case. - Provide the information you collected in the previous section. -Once it's filed: +Once it's filled: - The project team will label the issue accordingly. - A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. @@ -106,28 +108,27 @@ Once it's filed: ### Suggesting Enhancements -This section guides you through submitting an enhancement suggestion for TCGdex, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. +This section guides you through submitting an enhancement suggestion for OpenHealth, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. #### Before Submitting an Enhancement - Make sure that you are using the latest version. -- Read the [documentation](https://www.tcgdex.dev) carefully and find out if the functionality is already covered, maybe by an individual configuration. -- Perform a [search](https://github.com/tcgdex/javascript-sdk/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. +- Perform a [search](https://github.com/dzeiocom/OpenHealth/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. - Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library. #### How Do I Submit a Good Enhancement Suggestion? -Enhancement suggestions are tracked as [GitHub issues](https://github.com/tcgdex/javascript-sdk/issues). +Enhancement suggestions are tracked as [GitHub issues](https://github.com/dzeiocom/OpenHealth/issues). - Use a **clear and descriptive title** for the issue to identify the suggestion. - Provide a **step-by-step description of the suggested enhancement** in as many details as possible. - **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. -- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. -- **Explain why this enhancement would be useful** to most TCGdex users. You may also want to point out the other projects that solved it better and which could serve as inspiration. +- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. +- **Explain why this enhancement would be useful** to most OpenHealth users. You may also want to point out the other projects that solved it better and which could serve as inspiration. @@ -137,14 +138,10 @@ _note: Follow the different styleguides listed below when contributing_ - Fork 🍴 the project. _see the `fork` button at the top right of the screen_ - make the changes you want in your repository. -- Create a Pull request here https://github.com/tcgdex/javascript-sdk/compare by selecting your repository patch with our `master` branch _If it's not finished put WIP: before_ +- Create a Pull request here https://github.com/dzeiocom/OpenHealth/compare by selecting your repository patch with our `master` branch _If it's not finished put WIP: before_ - we don't like ❌, so if your pull request has its automated checks ending with the red cross, please double check your changes until it show the awesome 🟢, or ask for help ! - If your pull request is ready for review remove WIP: put it s ready for review and we will handle the rest ! -### Improving The Documentation - -The documentation is updated in the Documentation repository at - ## Styleguides ### Coding Guidelines @@ -184,4 +181,4 @@ In short, please name your Pull Requests/Commits following this format ## Attribution -This guide is based on [Contribution Gen]((https://github.com/bttger/contributing-gen)) and was adapted by the TCGdex community ! +This guide is based on [Contribution Gen]((https://github.com/bttger/contributing-gen)) and was adapted by the OpenHealth community ! diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..54fa6ed --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Florian Bouillon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index c487018..8abe0ce 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,43 @@ -

- - NOM Version - - - NPM Downloads - - - npm version - - - Github stars - - - the TCGdex JAvascript SDK is released under the MIT license. - - - Discord Link - -

+# ![App Icon](./app/src/main/ic_launcher-playstore.png) Open Health -# Open Health +Your privacy-friendly FOSS Health Application -TODO: Make a good text here -TODO: Change CONTRIBUTING.md to be correct + + Get it on F-Droid + + + Get it on Google Play + + +## Features + +- Material 3 Design ! +- Log Multiple Health information like your Weight, Water Intake, Food Consumption and Steps +- An enormous Food Database through OpenFoodFact ! +- no account creation ! +- External Services supported (not available on F-Droid) : Google Fit, Samsung Health and Withings Mate +- Your data are entirely in your control ! (when not syncing to an external service) + +## Privacy and Permissions + +No Ads are served through this app. + +Permissions requests are for specifics usage and are only requests the first time they are needed: + +| Permission | Why is it requested | +| :--------------------: | :--------------------------------------------------------------- | +| ACCESS_FINE_LOCATION | Google Fit Extension Requirement (maybe not, still have to test) | +| ACCESS_COARSE_LOCATION | Same as above | +| ACTIVITY_RECOGNITION | Device Steps Usage | + +No other permissions are used (even the internet permission ;)). + +## Build + +- Install Android Studio +- Select your variant from the Build Variant pane (bottom left, default to debug) +- click on the debug icon for debug +- it will be running on your emulator/device ## Contributing @@ -31,9 +46,7 @@ See [CONTRIBUTING.md](https://github.com/dzeiocom/OpenHealth/blob/master/CONTRIB TL::DR - Fork - - Commit your changes - - Pull Request on this Repository ## License diff --git a/app/build.gradle b/app/build.gradle index 0ec3f07..674b073 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,6 +51,12 @@ android { versionName "1.0.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + // Languages + def locales = ["en", "fr"] + + buildConfigField "String[]", "LOCALES", "new String[]{\""+locales.join("\",\"")+"\"}" + resConfigs locales } buildTypes { @@ -143,4 +149,4 @@ dependencies { testImplementation "androidx.room:room-testing:$room_version" -} \ No newline at end of file +} diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml index 5863d12..84cd8f4 100644 --- a/app/src/debug/res/values/strings.xml +++ b/app/src/debug/res/values/strings.xml @@ -1,4 +1,4 @@ - OpenHealth - Debug - \ No newline at end of file + OpenHealth - Debug + diff --git a/app/src/main/java/com/dzeio/openhealth/Application.kt b/app/src/main/java/com/dzeio/openhealth/Application.kt index bb2ee43..f5ad855 100644 --- a/app/src/main/java/com/dzeio/openhealth/Application.kt +++ b/app/src/main/java/com/dzeio/openhealth/Application.kt @@ -1,8 +1,13 @@ package com.dzeio.openhealth import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import android.content.res.Resources +import androidx.preference.PreferenceManager import com.google.android.material.color.DynamicColors import dagger.hilt.android.HiltAndroidApp +import java.util.Locale @HiltAndroidApp class Application : Application() { @@ -15,5 +20,16 @@ class Application : Application() { // Android Dynamics Colors DynamicColors.applyToActivitiesIfAvailable(this) + + // Change application Language based on setting + val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) + val lang = preferences.getString("global_language", Locale.getDefault().language) + val locale = Locale(lang) + Locale.setDefault(locale) + + val overrideConfiguration = baseContext.resources.configuration + overrideConfiguration.locale = locale + val context: Context = createConfigurationContext(overrideConfiguration) + val resources: Resources = context.getResources() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dzeio/openhealth/MainActivity.kt b/app/src/main/java/com/dzeio/openhealth/MainActivity.kt index e8c9d8c..9d84208 100644 --- a/app/src/main/java/com/dzeio/openhealth/MainActivity.kt +++ b/app/src/main/java/com/dzeio/openhealth/MainActivity.kt @@ -57,7 +57,6 @@ class MainActivity : BaseActivity() { WorkManager.getInstance(this) .cancelAllWork() WaterReminderService.setup(this) - } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -107,7 +106,6 @@ class MainActivity : BaseActivity() { Log.e("MainActivity", "Error Creating Notification Channel", e) } } - } } @@ -125,9 +123,13 @@ class MainActivity : BaseActivity() { ) true } + R.id.action_about -> { + navController.navigate( + HomeFragmentDirections.actionNavHomeToAboutFragment() + ) + true + } else -> super.onOptionsItemSelected(item) } - } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dzeio/openhealth/adapters/WaterAdapter.kt b/app/src/main/java/com/dzeio/openhealth/adapters/WaterAdapter.kt index 27dee4a..e9a1ba8 100644 --- a/app/src/main/java/com/dzeio/openhealth/adapters/WaterAdapter.kt +++ b/app/src/main/java/com/dzeio/openhealth/adapters/WaterAdapter.kt @@ -20,7 +20,7 @@ class WaterAdapter() : BaseAdapter() { position: Int ) { holder.binding.value.text = "${item.value}ml" - holder.binding.datetime.text = "${item.formatTimestamp()} ${item.timestamp}" + holder.binding.datetime.text = "${item.formatTimestamp()}" holder.binding.edit.setOnClickListener { onItemClick?.invoke(item) } diff --git a/app/src/main/java/com/dzeio/openhealth/adapters/WeightAdapter.kt b/app/src/main/java/com/dzeio/openhealth/adapters/WeightAdapter.kt index 783ccba..78e54a7 100644 --- a/app/src/main/java/com/dzeio/openhealth/adapters/WeightAdapter.kt +++ b/app/src/main/java/com/dzeio/openhealth/adapters/WeightAdapter.kt @@ -6,8 +6,11 @@ import com.dzeio.openhealth.core.BaseAdapter import com.dzeio.openhealth.core.BaseViewHolder import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.databinding.LayoutItemListBinding +import com.dzeio.openhealth.units.WeightUnit -class WeightAdapter() : BaseAdapter() { +class WeightAdapter : BaseAdapter() { + + var unit: WeightUnit = WeightUnit.KG override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> LayoutItemListBinding get() = LayoutItemListBinding::inflate @@ -19,10 +22,11 @@ class WeightAdapter() : BaseAdapter() { item: Weight, position: Int ) { - holder.binding.value.text = "${item.weight}kg" + val weightTxt = String.format("%.1f", item.weight * unit.fromKG) + holder.binding.value.text = "$weightTxt${unit.unit}" holder.binding.datetime.text = item.formatTimestamp() holder.binding.edit.setOnClickListener { onItemClick?.invoke(item) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseFragment.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseFragment.kt index 75e4109..e69a93a 100644 --- a/app/src/main/java/com/dzeio/openhealth/core/BaseFragment.kt +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseFragment.kt @@ -1,48 +1,14 @@ package com.dzeio.openhealth.core -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.viewbinding.ViewBinding -abstract class BaseFragment(private val viewModelClass: Class) : Fragment() { +abstract class BaseFragment( + private val viewModelClass: Class +) : + BaseStaticFragment() { + val viewModel by lazy { ViewModelProvider(this)[viewModelClass] } - - private var _binding: VB? = null - val binding get() = _binding!! - - /** - * Setup everything! - */ - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - super.onCreateView(inflater, container, savedInstanceState) - - _binding = bindingInflater(inflater, container, false) - - return binding.root - } - - /** - * Function to inflate the Fragment Bindings - * - * use like this: `ViewBinding::inflater` - */ - abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB - - /** - * Destroy binding - */ - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dzeio/openhealth/core/BaseStaticFragment.kt b/app/src/main/java/com/dzeio/openhealth/core/BaseStaticFragment.kt new file mode 100644 index 0000000..a97cbeb --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/core/BaseStaticFragment.kt @@ -0,0 +1,44 @@ +package com.dzeio.openhealth.core + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.viewbinding.ViewBinding + +abstract class BaseStaticFragment : Fragment() { + + private var _binding: VB? = null + val binding get() = _binding!! + + /** + * Setup everything! + */ + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + super.onCreateView(inflater, container, savedInstanceState) + + _binding = bindingInflater(inflater, container, false) + + return binding.root + } + + /** + * Function to inflate the Fragment Bindings + * + * use like this: `ViewBinding::inflater` + */ + abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB + + /** + * Destroy binding + */ + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/app/src/main/java/com/dzeio/openhealth/di/SystemModule.kt b/app/src/main/java/com/dzeio/openhealth/di/SystemModule.kt new file mode 100644 index 0000000..880b89f --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/di/SystemModule.kt @@ -0,0 +1,22 @@ +package com.dzeio.openhealth.di + +import android.content.Context +import android.content.SharedPreferences +import androidx.preference.PreferenceManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@InstallIn(SingletonComponent::class) +@Module +class SystemModule { + + @Singleton + @Provides + fun provideSettings(@ApplicationContext context: Context): SharedPreferences { + return PreferenceManager.getDefaultSharedPreferences(context) + } +} diff --git a/app/src/main/java/com/dzeio/openhealth/graphs/WeightChart.kt b/app/src/main/java/com/dzeio/openhealth/graphs/WeightChart.kt new file mode 100644 index 0000000..03fd993 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/graphs/WeightChart.kt @@ -0,0 +1,143 @@ +package com.dzeio.openhealth.graphs + +import android.graphics.Color +import android.view.View +import com.dzeio.openhealth.data.weight.Weight +import com.dzeio.openhealth.units.Units +import com.dzeio.openhealth.utils.GraphUtils +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.LimitLine +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.google.android.material.color.MaterialColors +import kotlin.math.max +import kotlin.math.min + +object WeightChart { + fun setup( + chart: LineChart, + view: View, + data: List, + modifier: Units.Mass, + goal: Float?, + limit: Boolean = true + ) { + GraphUtils.lineChartSetup( + chart, + MaterialColors.getColor( + view, + com.google.android.material.R.attr.colorPrimary + ), + MaterialColors.getColor( + view, + com.google.android.material.R.attr.colorOnBackground + ) + ) + + if (data.isEmpty()) { + return + } + + // Axis Max/Min + var axisMin = max(data.minOf { it.weight } - 10, 0f) + var axisMax = data.maxOf { it.weight } + 10 + + if (goal != null) { + axisMax = max(axisMax, goal) + axisMin = min(axisMin, goal) + } + + // Average calculation + val averageCalculation = min(30, max(3, data.size / 2)) + val isEven = averageCalculation % 2 == 1 + val midValue = averageCalculation / 2 + + val averageYs = data.mapIndexed { index, entry -> + var minItem = index - midValue + var maxItem = index + if (!isEven) midValue + 1 else midValue + val lastEntry = data.size - 1 + if (minItem < 0) { + maxItem += kotlin.math.abs(minItem) + minItem = 0 + } + if (maxItem >= lastEntry) { + val diff = maxItem - lastEntry + minItem = max(0, minItem - diff) + maxItem -= diff + } + + var average = 0f + for (i in minItem..maxItem) { + average += data[i].weight + } + + return@mapIndexed Entry( + entry.timestamp.toFloat(), + (average / (maxItem - minItem + 1)) * modifier.modifier + ) + } + + val rawData = GraphUtils.lineDataSet( + LineDataSet( + data.mapIndexed { _, weight -> + return@mapIndexed Entry( + weight.timestamp.toFloat(), + weight.weight * modifier.modifier + ) + }, + "Weight" + ) + ).apply { + axisDependency = YAxis.AxisDependency.RIGHT + } + + val averageData = GraphUtils.lineDataSet(LineDataSet(averageYs, "Average")).apply { + axisDependency = YAxis.AxisDependency.RIGHT + color = Color.GREEN + } + + val entries = ArrayList() + for (item in data) { + entries.add( + Entry( + item.timestamp.toFloat(), + item.weight * modifier.modifier + ) + ) + } + + chart.apply { + + this.data = LineData(rawData, averageData) + + val twoWeeks = (data[data.size - 1].timestamp - data[0].timestamp) > 1290000000f + + if (twoWeeks && limit) { + // idk what I did but it works lol + setVisibleXRange( + 0f, data[data.size - 1].timestamp / 1000f + ) + + axisRight.axisMinimum = axisMin * modifier.modifier + axisRight.axisMaximum = axisMax * modifier.modifier + + // BIS... :( + // Also it invalidate the view so I don't have to call invalidate + moveViewToX(data[data.size - 1].timestamp - 1290000000f) + } + + if (goal != null) { + val limit = LimitLine(goal * modifier.modifier) + limit.lineColor = Color.RED + val dash = 30f + limit.enableDashedLine(dash, dash, 1f) + limit.lineWidth = 1f + limit.textColor = Color.BLACK + + axisRight.addLimitLine(limit) + } + } + } +} diff --git a/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationChannels.kt b/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationChannels.kt index b64a600..9ee9bf3 100644 --- a/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationChannels.kt +++ b/app/src/main/java/com/dzeio/openhealth/interfaces/NotificationChannels.kt @@ -6,5 +6,5 @@ enum class NotificationChannels( val importance: Int ) { // 3 is IMPORTANCE_DEFAULT - DEFAULT("openhealth_default", "Default Channel", 3) -} \ No newline at end of file + WATER("water", "Water Notifications", 3) +} diff --git a/app/src/main/java/com/dzeio/openhealth/services/WaterReminderService.kt b/app/src/main/java/com/dzeio/openhealth/services/WaterReminderService.kt index 0fc0881..97ce28d 100644 --- a/app/src/main/java/com/dzeio/openhealth/services/WaterReminderService.kt +++ b/app/src/main/java/com/dzeio/openhealth/services/WaterReminderService.kt @@ -8,6 +8,7 @@ import android.os.Build import android.util.Log import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.navigation.NavDeepLinkBuilder import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkerParameters import com.dzeio.openhealth.Application @@ -42,23 +43,24 @@ class WaterReminderService( Log.d(TAG, "Ran! ${Date().toLocaleString()}") with(NotificationManagerCompat.from(context)) { val flag = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_IMMUTABLE else 0 + val intent = NavDeepLinkBuilder(context) + .setGraph(R.navigation.mobile_navigation) + .setDestination(R.id.nav_home) + // Will nav to water home when there will be a way to add it there + // .setDestination(R.id.nav_water_home) + .createTaskStackBuilder() + .getPendingIntent(0, flag) val builder = - NotificationCompat.Builder(context, NotificationChannels.DEFAULT.channelName) + NotificationCompat.Builder(context, NotificationChannels.WATER.id) .setContentTitle("Did you drink?") .setContentText("Drink now! ${Date().toLocaleString()}") .setSmallIcon(R.drawable.ic_logo_small) .setPriority(NotificationCompat.PRIORITY_HIGH) .setContentIntent( - PendingIntent.getActivity( - context, - 0, - Intent(context, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK - }, - flag - ) - ).build() + intent + ) + .build() notify( NotificationIds.WaterIntake.ordinal, builder @@ -66,4 +68,4 @@ class WaterReminderService( } return Result.success() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dzeio/openhealth/ui/about/AboutFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/about/AboutFragment.kt new file mode 100644 index 0000000..7c81ee2 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/ui/about/AboutFragment.kt @@ -0,0 +1,45 @@ +package com.dzeio.openhealth.ui.about + +import android.annotation.SuppressLint +import android.content.ActivityNotFoundException +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import com.dzeio.openhealth.BuildConfig +import com.dzeio.openhealth.R +import com.dzeio.openhealth.core.BaseStaticFragment +import com.dzeio.openhealth.databinding.FragmentAboutBinding + +class AboutFragment : BaseStaticFragment() { + override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentAboutBinding + get() = FragmentAboutBinding::inflate + + @SuppressLint("StringFormatInvalid") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.version.text = + resources.getString(R.string.version_number, BuildConfig.VERSION_NAME) + + binding.contactUs.setOnClickListener { + openLink("mailto:context.openhealth@dze.io") + } + + binding.github.setOnClickListener { + openLink("https://github.com/dzeiocom/OpenHealth") + } + } + + private fun openLink(url: String) { + try { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + startActivity(intent) + } catch (e: ActivityNotFoundException) { + Toast.makeText(requireContext(), "Could not handle link $url", Toast.LENGTH_LONG).show() + } + } +} diff --git a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt index df0ffc3..dc460c4 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeFragment.kt @@ -1,9 +1,9 @@ package com.dzeio.openhealth.ui.home import android.animation.ValueAnimator +import android.content.SharedPreferences import android.graphics.Bitmap import android.graphics.Canvas -import android.graphics.Color import android.graphics.RectF import android.os.Bundle import android.util.Log @@ -13,74 +13,61 @@ import android.view.ViewGroup import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager -import com.dzeio.openhealth.Application import com.dzeio.openhealth.core.BaseFragment import com.dzeio.openhealth.data.water.Water import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.databinding.FragmentHomeBinding +import com.dzeio.openhealth.graphs.WeightChart import com.dzeio.openhealth.ui.weight.AddWeightDialog +import com.dzeio.openhealth.units.UnitFactory import com.dzeio.openhealth.utils.DrawUtils import com.dzeio.openhealth.utils.GraphUtils -import com.github.mikephil.charting.components.LimitLine -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.google.android.material.color.MaterialColors import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch import kotlin.math.min -import kotlin.properties.Delegates +import kotlinx.coroutines.flow.collectLatest @AndroidEntryPoint class HomeFragment : BaseFragment(HomeViewModel::class.java) { - companion object { - const val TAG = "${Application.TAG}/HomeFragment" - } - override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentHomeBinding get() = FragmentHomeBinding::inflate - private var intake by Delegates.notNull() + private val settings: SharedPreferences by lazy { + PreferenceManager.getDefaultSharedPreferences(requireContext()) + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + // Bindings binding.addWeight.setOnClickListener { AddWeightDialog().show(requireActivity().supportFragmentManager, null) } binding.fragmentHomeWaterAdd.setOnClickListener { - val water = viewModel.water.value - - if (water == null) { - + if (water == null || !water.isToday()) { val w = Water() - w.value = 200 + w.value = viewModel.waterCupSize viewModel.updateWater(w) } else { - water.value += 200 + water.value += viewModel.waterCupSize viewModel.updateWater(water) } } - intake = PreferenceManager.getDefaultSharedPreferences(requireContext()) - .getString("water_intake", "1200")?.toFloat() ?: 1200f - - binding.fragmentHomeWaterTotal.text = "${intake.toInt()}ml" - - binding.fragmentHomeWaterRemove.setOnClickListener { _ -> + binding.fragmentHomeWaterTotal.text = + String.format( + resources.getString(viewModel.waterUnit.unit), + viewModel.dailyWaterIntake + ) + binding.fragmentHomeWaterRemove.setOnClickListener { val water = viewModel.water.value - if (water != null) { - - water.value -= 200 - if (water.value == 0) { + water.value -= viewModel.waterCupSize + if (water.value <= 0) { viewModel.deleteWater(water) } else { viewModel.updateWater(water) @@ -88,22 +75,6 @@ class HomeFragment : BaseFragment(HomeViewMo } } - binding.fragmentHomeWaterRemove.setOnClickListener { - lifecycleScope.launch { - - val item = viewModel.fetchTodayWater().first() - Log.d(TAG, "Collected latest $it") - if (item != null) { - item.value -= 200 - if (item.value == 0) { - viewModel.deleteWater(item) - } else { - viewModel.updateWater(item) - } - } - } - } - binding.listWeight.setOnClickListener { findNavController().navigate(HomeFragmentDirections.actionNavHomeToNavListWeight()) } @@ -126,32 +97,13 @@ class HomeFragment : BaseFragment(HomeViewMo } private fun updateGraph(list: List) { - if (list.isNotEmpty()) { - val entries = ArrayList() - for (item in list) { - entries.add(Entry(item.timestamp.toFloat(), item.weight)) - } - - val dataSet = LineDataSet(entries, "Label").apply { - axisDependency = YAxis.AxisDependency.RIGHT - setDrawCircles(false) - setDrawCircleHole(false) - mode = LineDataSet.Mode.HORIZONTAL_BEZIER - } - - binding.weightGraph.apply { - - // Apply new dataset - - data = LineData(dataSet) - - // idk what I did but it works lol - setVisibleXRange( - 0f, entries[entries.size - 1].x / 1000f - ) - - val goal = PreferenceManager.getDefaultSharedPreferences(requireContext()) - .getString("weight_goal", null)?.toFloatOrNull() + WeightChart.setup( + binding.weightGraph, + requireView(), + list, + viewModel.weightUnit, + viewModel.goalWeight?.toFloat() + ) // legend.apply { // isEnabled = true @@ -165,29 +117,6 @@ class HomeFragment : BaseFragment(HomeViewMo // setCustom(arrayOf(legendEntry)) // } // } - - setDrawBorders(false) - - // BIS... :( - // Also it invalidate the view so I don't have to call invalidate - moveViewToX(entries[entries.size - 1].x - 1600000000f) - - if (goal != null) { - axisRight.axisMinimum = goal - val limit = LimitLine(goal) - limit.lineColor = Color.RED - val dash = 30f - limit.enableDashedLine(dash, dash, 0f) - limit.lineWidth = 1f - limit.textColor = Color.BLACK - limit.textSize = 12f - - axisRight.addLimitLine(limit) - } else { - isAutoScaleMinMaxEnabled = true - } - } - } } override fun onStart() { @@ -197,23 +126,28 @@ class HomeFragment : BaseFragment(HomeViewMo viewModel.fetchWeights().collectLatest { updateGraph(it) } - updateWater(0) - updateWater(1234) + updateWater(0, 1) } viewModel.water.observe(viewLifecycleOwner) { - Log.d(TAG, "${it?.formatTimestamp()} $it") if (it != null) { - updateWater(it.value) + updateWater(0, it.value) } else { - updateWater(0) + updateWater(0, 1) } } } - private fun updateWater(water: Int) { - val oldValue = binding.fragmentHomeWaterCurrent.text.toString().replace("ml", "").toInt() - binding.fragmentHomeWaterCurrent.text = "${water}ml" + private fun updateWater(oldValue: Int, newValue: Int) { + + val waterUnit = + UnitFactory.volume(settings.getString("water_unit", "milliliter") ?: "Milliliter") + + binding.fragmentHomeWaterCurrent.text = + String.format( + resources.getString(waterUnit.unit), + (newValue * waterUnit.modifier).toInt() + ) var width = 1500 var height = 750 @@ -260,25 +194,27 @@ class HomeFragment : BaseFragment(HomeViewMo 3f ) - Log.d("Test", "${min(oldValue.toFloat(), intake)} ${min(water.toFloat(), intake)}") - ValueAnimator.ofFloat(min(oldValue.toFloat(), intake), min(water.toFloat(), intake)) - .apply { - duration = 300 - addUpdateListener { - DrawUtils.drawArc( - canvas, - 100 * it.animatedValue as Float / intake, - rect, - MaterialColors.getColor( - requireView(), - com.google.android.material.R.attr.colorPrimary - ), - 6f - ) - canvas.save() - binding.background.setImageBitmap(graph) - } - start() + ValueAnimator.ofInt( + min(oldValue, viewModel.dailyWaterIntake), + min(newValue, viewModel.dailyWaterIntake) + ).apply { + duration = 300 + addUpdateListener { + + DrawUtils.drawArc( + canvas, + 100 * it.animatedValue as Int / viewModel.dailyWaterIntake.toFloat(), + rect, + MaterialColors.getColor( + requireView(), + com.google.android.material.R.attr.colorPrimary + ), + 6f + ) + canvas.save() + binding.background.setImageBitmap(graph) } + start() + } } } diff --git a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt index b1f824a..0ccef05 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/home/HomeViewModel.kt @@ -1,30 +1,62 @@ package com.dzeio.openhealth.ui.home +import android.content.SharedPreferences +import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope +import com.dzeio.openhealth.Application.Companion.TAG import com.dzeio.openhealth.core.BaseViewModel import com.dzeio.openhealth.data.water.Water import com.dzeio.openhealth.data.water.WaterRepository import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.data.weight.WeightRepository +import com.dzeio.openhealth.units.UnitFactory import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject internal constructor( private val weightRepository: WeightRepository, - private val waterRepository: WaterRepository + private val waterRepository: WaterRepository, + settings: SharedPreferences ) : BaseViewModel() { + private val _water = MutableLiveData(null) + val water: LiveData = _water + + var waterCupSize = settings.getInt("water_cup_size", 200) + + var waterUnit = + UnitFactory.volume(settings.getString("water_unit", "milliliter") ?: "Milliliter") + + var weightUnit = + UnitFactory.mass(settings.getString("weight_unit", "kilogram") ?: "kilogram") + + val goalWeight: Int? = + (settings.getString("weight_goal", null)?.toIntOrNull()) + + val dailyWaterIntake: Int = + ((settings.getString("water_intake", "1200")?.toFloatOrNull() ?: 1200f) * waterUnit.modifier) + .toInt() + init { viewModelScope.launch { waterRepository.todayWater().collectLatest { _water.value = it } } + // don't listen for prefs changes + settings.registerOnSharedPreferenceChangeListener { _, key -> + Log.d(TAG, "Pref changed: $key") + when (key) { + "water_cup_size" -> { + waterCupSize = settings.getInt("water_cup_size", 200) + } + } + } } /** @@ -32,7 +64,6 @@ class HomeViewModel @Inject internal constructor( */ fun fetchWeights() = weightRepository.getWeights() - /** * @deprecated */ @@ -44,11 +75,7 @@ class HomeViewModel @Inject internal constructor( suspend fun addWeight(weight: Weight) = weightRepository.addWeight(weight) - suspend fun fetchTodayWater() = waterRepository.todayWater() - - private val _water = MutableLiveData(null) - val water: LiveData = _water - + fun fetchTodayWater() = waterRepository.todayWater() fun updateWater(water: Water) { viewModelScope.launch { @@ -62,4 +89,4 @@ class HomeViewModel @Inject internal constructor( _water.postValue(null) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dzeio/openhealth/ui/settings/SettingsFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/settings/SettingsFragment.kt index cc7c45a..f6a29b3 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/settings/SettingsFragment.kt @@ -1,18 +1,99 @@ package com.dzeio.openhealth.ui.settings +import android.content.SharedPreferences +import android.content.res.Configuration import android.os.Bundle import android.text.InputType +import android.util.Log import androidx.preference.EditTextPreference +import androidx.preference.ListPreference import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceManager +import com.dzeio.openhealth.BuildConfig import com.dzeio.openhealth.R +import com.dzeio.openhealth.units.UnitFactory +import java.util.Locale class SettingsFragment : PreferenceFragmentCompat() { + + val settings: SharedPreferences by lazy { + PreferenceManager.getDefaultSharedPreferences(requireContext()) + } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.preferences, rootKey) + // Force only numbers on Goal val weightGoal = findPreference("weight_goal") - weightGoal?.setOnBindEditTextListener { - it.inputType = InputType.TYPE_CLASS_NUMBER + weightGoal?.apply { + setOnBindEditTextListener { + it.inputType = InputType.TYPE_CLASS_NUMBER + } + val value = settings.getString("weight_goal", null) + val modifier = UnitFactory.mass(settings.getString("weight_unit", null) ?: "kilogram") + if (value != null && value.isNotEmpty()) { + text = (value.toFloat() * modifier.modifier).toString() + } + setOnPreferenceChangeListener { _, newValue -> + val alue = ((newValue as String).toInt() / modifier.modifier).toInt().toString() + settings.edit() + .putString( + "weight_goal", + alue + ) + .apply() + text = alue + return@setOnPreferenceChangeListener false + } + } + + // 251 kg + // 553 lb + + findPreference("weight_unit")?.apply { + setOnPreferenceChangeListener { _, newValue -> + val unit = settings.getString("weight_unit", "kilogram") + ?: return@setOnPreferenceChangeListener true + val goal = settings.getString("weight_goal", null) + ?: return@setOnPreferenceChangeListener true + val modifier = UnitFactory.mass(newValue as String) + val oldModifier = UnitFactory.mass(unit) + val value = + (goal.toFloat() / oldModifier.modifier * modifier.modifier).toInt().toString() + settings.edit() + .putString( + "weight_goal", + value + ) + .apply() + weightGoal?.text = value + return@setOnPreferenceChangeListener true + } + } + + val languagesPreference = findPreference("global_language") + Log.d("TAG", Locale.getDefault().language) + languagesPreference?.apply { + entries = BuildConfig.LOCALES + entryValues = BuildConfig.LOCALES + setDefaultValue(Locale.getDefault().language) + } + + // Update App Locale + languagesPreference?.setOnPreferenceChangeListener { _, newValue -> + val locale = Locale(newValue as String) + Locale.setDefault(locale) + val config = Configuration() + config.locale = locale + + requireActivity().baseContext.resources.updateConfiguration( + config, + requireActivity().baseContext.resources.displayMetrics + ) + + requireActivity().recreate() + + return@setOnPreferenceChangeListener true } } } diff --git a/app/src/main/java/com/dzeio/openhealth/ui/water/EditWaterDialog.kt b/app/src/main/java/com/dzeio/openhealth/ui/water/EditWaterDialog.kt index 2b683d7..916490a 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/water/EditWaterDialog.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/water/EditWaterDialog.kt @@ -75,7 +75,6 @@ class EditWaterDialog : water.timestamp = tsp binding.date.setText(water.formatTimestamp()) - } datePicker.show(fragManager, "dialog") Log.d("Tag", "${date.year + 1900}, ${date.month}, ${date.day}") @@ -88,8 +87,6 @@ class EditWaterDialog : } else { TODO("VERSION.SDK_INT < N") } - - } viewModel.init(args.id) @@ -133,6 +130,5 @@ class EditWaterDialog : } else -> super.onOptionsItemSelected(item) } - } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dzeio/openhealth/ui/water/WaterHomeFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/water/WaterHomeFragment.kt index 2a9d5a0..f92bd92 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/water/WaterHomeFragment.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/water/WaterHomeFragment.kt @@ -1,13 +1,11 @@ package com.dzeio.openhealth.ui.water import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager -import com.dzeio.openhealth.Application.Companion.TAG import com.dzeio.openhealth.adapters.WaterAdapter import com.dzeio.openhealth.core.BaseFragment import com.dzeio.openhealth.databinding.FragmentMainWaterHomeBinding @@ -17,9 +15,6 @@ import com.github.mikephil.charting.data.BarDataSet import com.github.mikephil.charting.data.BarEntry import com.google.android.material.color.MaterialColors import dagger.hilt.android.AndroidEntryPoint -import java.util.Calendar -import java.util.Date -import java.util.TimeZone @AndroidEntryPoint class WaterHomeFragment : @@ -62,6 +57,10 @@ class WaterHomeFragment : ) ) + binding.buttonEditDefaultIntake.setOnClickListener { + findNavController().navigate(WaterHomeFragmentDirections.actionNavWaterHomeToNavWaterSizeDialog()) + } + chart.xAxis.valueFormatter = GraphUtils.DateValueFormatter(1000 * 60 * 60 * 24) viewModel.items.observe(viewLifecycleOwner) { list -> diff --git a/app/src/main/java/com/dzeio/openhealth/ui/water/WaterSizeSelectorDialog.kt b/app/src/main/java/com/dzeio/openhealth/ui/water/WaterSizeSelectorDialog.kt index 963537e..e554d4a 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/water/WaterSizeSelectorDialog.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/water/WaterSizeSelectorDialog.kt @@ -1,13 +1,102 @@ package com.dzeio.openhealth.ui.water +import android.graphics.Color import android.view.LayoutInflater +import com.dzeio.openhealth.R import com.dzeio.openhealth.core.BaseDialog import com.dzeio.openhealth.databinding.DialogWaterSizeSelectorBinding +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class WaterSizeSelectorDialog : BaseDialog( WaterSizeSelectorViewModel::class.java ) { + override val bindingInflater: (LayoutInflater) -> DialogWaterSizeSelectorBinding get() = DialogWaterSizeSelectorBinding::inflate + + override fun onCreated() { + super.onCreated() + + binding.cancel.setOnClickListener { + dismiss() + } + + binding.validate.setOnClickListener { + dismiss() + } + + viewModel.cupSize.observe(this) { + binding.customSizeText.text = String.format( + getString(R.string.custom_amount), + "${it}ml" + ) + binding.size100ml.setBackgroundColor(Color.TRANSPARENT) + binding.size250ml.setBackgroundColor(Color.TRANSPARENT) + binding.size500ml.setBackgroundColor(Color.TRANSPARENT) + binding.size1000ml.setBackgroundColor(Color.TRANSPARENT) + binding.customSize.setBackgroundColor(Color.TRANSPARENT) + val back = resources.getColor( + com.google.android.material.R.color.material_dynamic_primary95, + ) + when (it) { + 100 -> { + binding.size100ml.setBackgroundColor(back) + } + 250 -> { + binding.size250ml.setBackgroundColor(back) + } + 500 -> { + binding.size500ml.setBackgroundColor(back) + } + 1000 -> { + binding.size1000ml.setBackgroundColor(back) + } + else -> { + binding.customSize.setBackgroundColor(back) + } + } + } + + binding.size100ml.setOnClickListener { + viewModel.setCupSize(100) + } + binding.size250ml.setOnClickListener { + viewModel.setCupSize(250) + } + binding.size500ml.setOnClickListener { + viewModel.setCupSize(500) + } + binding.size1000ml.setOnClickListener { + viewModel.setCupSize(1000) + } + + binding.customSize.setOnClickListener { + val editTextLayout = TextInputLayout(requireContext()) + + val editText = TextInputEditText(requireContext()) + editText.setText(viewModel.cupSize.value.toString()) + + editTextLayout.addView(editText) + + MaterialAlertDialogBuilder(requireContext()) + .setView(editTextLayout) + .setTitle("Custom Cup Size") + .setOnCancelListener { + it.dismiss() + } + .setPositiveButton( + R.string.validate + ) { dialog, _ -> + viewModel.setCupSize(editText.text.toString().toInt()) + dismiss() + dialog.dismiss() + } + .show() + } + } } diff --git a/app/src/main/java/com/dzeio/openhealth/ui/water/WaterSizeSelectorViewModel.kt b/app/src/main/java/com/dzeio/openhealth/ui/water/WaterSizeSelectorViewModel.kt index bd34769..6e3c9b0 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/water/WaterSizeSelectorViewModel.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/water/WaterSizeSelectorViewModel.kt @@ -1,6 +1,32 @@ package com.dzeio.openhealth.ui.water +import android.content.SharedPreferences +import androidx.lifecycle.MutableLiveData import com.dzeio.openhealth.core.BaseViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject -class WaterSizeSelectorViewModel : BaseViewModel() { +@HiltViewModel +class WaterSizeSelectorViewModel @Inject constructor( + private val settings: SharedPreferences +) : BaseViewModel() { + + private val _cupSize = MutableLiveData(0) + + val cupSize = _cupSize + + init { + val cup = settings.getInt("water_cup_size", -1) + if (cup != -1) { + _cupSize.value = cup + } + } + + fun setCupSize(value: Int) { + settings.edit() + .putInt("water_cup_size", value) + .apply() + + _cupSize.value = value + } } diff --git a/app/src/main/java/com/dzeio/openhealth/ui/weight/AddWeightDialog.kt b/app/src/main/java/com/dzeio/openhealth/ui/weight/AddWeightDialog.kt index a514195..c010e5d 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/weight/AddWeightDialog.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/weight/AddWeightDialog.kt @@ -29,7 +29,6 @@ class AddWeightDialog : BaseDialog(HomeVi setNegativeButton("Cancel") { dialog, _ -> dialog.cancel() } - } } @@ -40,7 +39,7 @@ class AddWeightDialog : BaseDialog(HomeVi viewModel.lastWeight().collect { if (it != null) { binding.kg.value = it.weight.toInt() - binding.gram.value = ((it.weight - it.weight.toInt()) * 10 ).toInt() + binding.gram.value = ((it.weight - it.weight.toInt()) * 10).toInt() } } } @@ -56,12 +55,11 @@ class AddWeightDialog : BaseDialog(HomeVi val weight = Weight().apply { weight = binding.kg.value + (binding.gram.value.toFloat() / 10) source = "OpenHealth" - } lifecycleScope.launchWhenCreated { viewModel.addWeight(weight) } - //callback?.invoke() + // callback?.invoke() dialog?.dismiss() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightFragment.kt b/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightFragment.kt index 8e30517..d96316d 100644 --- a/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightFragment.kt +++ b/app/src/main/java/com/dzeio/openhealth/ui/weight/ListWeightFragment.kt @@ -2,15 +2,24 @@ package com.dzeio.openhealth.ui.weight import android.os.Bundle import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager +import com.dzeio.openhealth.R import com.dzeio.openhealth.adapters.WeightAdapter import com.dzeio.openhealth.core.BaseFragment +import com.dzeio.openhealth.data.weight.Weight import com.dzeio.openhealth.databinding.FragmentListWeightBinding +import com.dzeio.openhealth.graphs.WeightChart import com.dzeio.openhealth.ui.home.HomeViewModel +import com.dzeio.openhealth.units.WeightUnit +import com.dzeio.openhealth.utils.GraphUtils +import com.google.android.material.color.MaterialColors import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest @@ -21,33 +30,82 @@ class ListWeightFragment : override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentListWeightBinding = FragmentListWeightBinding::inflate + val settings by lazy { + PreferenceManager.getDefaultSharedPreferences(requireContext()) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + setHasOptionsMenu(true) + val recycler = binding.list val manager = LinearLayoutManager(requireContext()) recycler.layoutManager = manager val adapter = WeightAdapter() + + val unit = settings.getString("weight_unit", "Kilogram") ?: "Kilogram" + + adapter.unit = WeightUnit.fromSettings(unit) + adapter.onItemClick = { findNavController().navigate( ListWeightFragmentDirections.actionNavListWeightToNavEditWeight( it.id ) ) - //EditWeightDialog().show(requireActivity().supportFragmentManager, "dialog") + // EditWeightDialog().show(requireActivity().supportFragmentManager, "dialog") } recycler.adapter = adapter viewLifecycleOwner.lifecycleScope.launchWhenCreated { viewModel.fetchWeights().collectLatest { + updateGraph(it) val itt = it.toMutableList() itt.sortWith { o1, o2 -> if (o1.timestamp > o2.timestamp) -1 else 1 } adapter.set(itt) } } - + GraphUtils.lineChartSetup( + binding.chart, + MaterialColors.getColor( + requireView(), + com.google.android.material.R.attr.colorPrimary + ), + MaterialColors.getColor( + requireView(), + com.google.android.material.R.attr.colorOnBackground + ) + ) } -} \ No newline at end of file + + private fun updateGraph(list: List) { + WeightChart.setup( + binding.chart, + requireView(), + list, + viewModel.weightUnit, + viewModel.goalWeight?.toFloat(), + false + ) + } + + override fun onPrepareOptionsMenu(menu: Menu) { + menu.findItem(R.id.action_add).isVisible = true + + super.onPrepareOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_add -> { + findNavController().navigate(ListWeightFragmentDirections.actionNavListWeightToNavAddWeightDialog()) + true + } + else -> super.onOptionsItemSelected(item) + } + } +} diff --git a/app/src/main/java/com/dzeio/openhealth/units/UnitFactory.kt b/app/src/main/java/com/dzeio/openhealth/units/UnitFactory.kt new file mode 100644 index 0000000..fb3198b --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/units/UnitFactory.kt @@ -0,0 +1,20 @@ +package com.dzeio.openhealth.units + +object UnitFactory { + fun mass(unit: String): Units.Mass { + return when (unit.lowercase()) { + "kilogram", "kilograms", "kg" -> Units.Mass.KILOGRAM + "pound", "pounds", "lb" -> Units.Mass.POUND + else -> Units.Mass.KILOGRAM + } + } + + fun volume(unit: String): Units.Volume { + return when (unit.lowercase()) { + "milliliter", "milliliters", "ml" -> Units.Volume.MILLILITER + "imperial ounce", "imperial ounces", "oz" -> Units.Volume.IMPERIAL_OUNCE + "us ounce", "us ounces" -> Units.Volume.US_OUNCE + else -> Units.Volume.MILLILITER + } + } +} diff --git a/app/src/main/java/com/dzeio/openhealth/units/Units.kt b/app/src/main/java/com/dzeio/openhealth/units/Units.kt new file mode 100644 index 0000000..1342ee5 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/units/Units.kt @@ -0,0 +1,57 @@ +package com.dzeio.openhealth.units + +import com.dzeio.openhealth.R + +object Units { + enum class Mass( + /** + * Value based on the Kilogram + */ + val modifier: Float, + val singular: Int, + val plural: Int, + val unit: Int + ) { + KILOGRAM( + 1f, + R.string.unit_mass_kilogram_name_singular, + R.string.unit_mass_kilogram_name_plural, + R.string.unit_mass_kilogram_unit + ), + POUND( + 0.45359237f, + R.string.unit_mass_pound_name_singular, + R.string.unit_mass_pound_name_plural, + R.string.unit_mass_pound_unit + ) + } + + enum class Volume( + /** + * Value based on the Kilogram + */ + val modifier: Float, + val singular: Int, + val plural: Int, + val unit: Int + ) { + MILLILITER( + 1f, + R.string.unit_volume_milliliter_name_singular, + R.string.unit_volume_milliliter_name_plural, + R.string.unit_volume_milliliter_unit + ), + IMPERIAL_OUNCE( + 0.03519503f, + R.string.unit_volume_imperial_ounce_name_singular, + R.string.unit_volume_imperial_ounce_name_plural, + R.string.unit_volume_ounce_unit + ), + US_OUNCE( + 0.03381413f, + R.string.unit_volume_us_ounce_name_singular, + R.string.unit_volume_us_ounce_name_plural, + R.string.unit_volume_ounce_unit + ) + } +} diff --git a/app/src/main/java/com/dzeio/openhealth/units/WaterUnit.kt b/app/src/main/java/com/dzeio/openhealth/units/WaterUnit.kt new file mode 100644 index 0000000..d8a9944 --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/units/WaterUnit.kt @@ -0,0 +1,21 @@ +package com.dzeio.openhealth.units + +enum class WaterUnit( + val unit: String, + val fromML: Float +) { + ML("ml", 1f), + US_OZ("oz", 0.03381413f), + IMP_OZ("oz", 0.03519503f); + + companion object { + fun fromSettings(value: String): WaterUnit { + return when (value.lowercase()) { + "milliliter" -> ML + "us ounce" -> US_OZ + "imperial ounce" -> IMP_OZ + else -> ML + } + } + } +} diff --git a/app/src/main/java/com/dzeio/openhealth/units/WeightUnit.kt b/app/src/main/java/com/dzeio/openhealth/units/WeightUnit.kt new file mode 100644 index 0000000..190c39a --- /dev/null +++ b/app/src/main/java/com/dzeio/openhealth/units/WeightUnit.kt @@ -0,0 +1,19 @@ +package com.dzeio.openhealth.units + +enum class WeightUnit( + val unit: String, + val fromKG: Float +) { + KG("kg", 1f), + LBS("lbs", 2.2046226218488f); + + companion object { + fun fromSettings(value: String): WeightUnit { + return when (value.lowercase()) { + "kilogram" -> KG + "pounds" -> LBS + else -> KG + } + } + } +} diff --git a/app/src/main/java/com/dzeio/openhealth/utils/GraphUtils.kt b/app/src/main/java/com/dzeio/openhealth/utils/GraphUtils.kt index e04c349..d7fd056 100644 --- a/app/src/main/java/com/dzeio/openhealth/utils/GraphUtils.kt +++ b/app/src/main/java/com/dzeio/openhealth/utils/GraphUtils.kt @@ -8,6 +8,7 @@ import com.github.mikephil.charting.components.Description import com.github.mikephil.charting.components.XAxis import com.github.mikephil.charting.data.BarLineScatterCandleBubbleData import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineDataSet import com.github.mikephil.charting.formatter.ValueFormatter import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet import java.text.SimpleDateFormat @@ -21,6 +22,14 @@ object GraphUtils { // chart.isAutoScaleMinMaxEnabled = true } + fun lineDataSet(lineDataSet: LineDataSet): LineDataSet { + return lineDataSet.apply { + setDrawCircles(false) + setDrawCircleHole(false) + mode = LineDataSet.Mode.HORIZONTAL_BEZIER + } + } + fun barChartSetup(chart: BarChart, mainColor: Int, textColor: Int) { barLineChartSetup(chart, mainColor, textColor) } diff --git a/app/src/main/res/drawable/ellipse.png b/app/src/main/res/drawable/ellipse.png deleted file mode 100644 index e8f8695..0000000 Binary files a/app/src/main/res/drawable/ellipse.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_baseline_line_style_24.xml b/app/src/main/res/drawable/ic_baseline_line_style_24.xml new file mode 100644 index 0000000..9261f47 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_line_style_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_logo_full.xml b/app/src/main/res/drawable/ic_logo_full.xml new file mode 100644 index 0000000..886d76b --- /dev/null +++ b/app/src/main/res/drawable/ic_logo_full.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_logo_github.xml b/app/src/main/res/drawable/ic_logo_github.xml new file mode 100644 index 0000000..9808a15 --- /dev/null +++ b/app/src/main/res/drawable/ic_logo_github.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_edit_24.xml b/app/src/main/res/drawable/ic_outline_edit_24.xml new file mode 100644 index 0000000..47ea705 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_edit_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_free_breakfast_24.xml b/app/src/main/res/drawable/ic_outline_free_breakfast_24.xml new file mode 100644 index 0000000..f95525d --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_free_breakfast_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_local_drink_24.xml b/app/src/main/res/drawable/ic_outline_local_drink_24.xml new file mode 100644 index 0000000..90dd1da --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_local_drink_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_mail_24.xml b/app/src/main/res/drawable/ic_outline_mail_24.xml new file mode 100644 index 0000000..a2bac15 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_mail_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/dialog_water_size_selector.xml b/app/src/main/res/layout/dialog_water_size_selector.xml index 869e11f..4bf3aac 100644 --- a/app/src/main/res/layout/dialog_water_size_selector.xml +++ b/app/src/main/res/layout/dialog_water_size_selector.xml @@ -1,7 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +