Skip to content

Commit

Permalink
Reviewed new model for main fragment screen (isse #13) - need tests w…
Browse files Browse the repository at this point in the history
…ith Lottie performance impacts
  • Loading branch information
Javinator9889 committed Jun 26, 2020
1 parent f7154ad commit 3222c4e
Show file tree
Hide file tree
Showing 18 changed files with 373 additions and 173 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ android {
applicationId "com.javinator9889.handwashingreminder"
minSdkVersion 17
targetSdkVersion 29
versionCode 126
versionCode 130
versionName "1.2.0-${gitCommitHash}"
multiDexEnabled true
resConfigs "en", "es"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,23 @@ package com.javinator9889.handwashingreminder.activities.views.fragments.disease
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import androidx.annotation.StringRes
import androidx.core.app.ActivityCompat
import androidx.core.view.ViewCompat
import androidx.emoji.text.EmojiCompat
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.observe
import androidx.lifecycle.whenStarted
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.github.mikephil.charting.data.BarData
import com.github.mikephil.charting.data.BarDataSet
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import com.google.android.material.textview.MaterialTextView
import com.javinator9889.handwashingreminder.R
import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView
import com.javinator9889.handwashingreminder.activities.base.LayoutVisibilityChange
Expand All @@ -42,6 +48,7 @@ import com.javinator9889.handwashingreminder.activities.views.viewmodels.SavedVi
import com.javinator9889.handwashingreminder.data.ParsedHTMLText
import com.javinator9889.handwashingreminder.data.room.entities.Handwashing
import com.javinator9889.handwashingreminder.data.viewmodels.HandwashingViewModel
import com.javinator9889.handwashingreminder.emoji.EmojiLoader
import com.javinator9889.handwashingreminder.utils.calendar.CalendarUtils
import com.javinator9889.handwashingreminder.utils.toBarEntry
import com.mikepenz.fastadapter.FastAdapter
Expand All @@ -52,6 +59,8 @@ import kotlinx.android.synthetic.main.handwash_count.*
import kotlinx.android.synthetic.main.handwash_count.view.*
import kotlinx.android.synthetic.main.loading_recycler_view.*
import kotlinx.android.synthetic.main.loading_recycler_view.view.*
import kotlinx.android.synthetic.main.main_disease_view.view.*
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.launch
import timber.log.Timber

Expand All @@ -61,6 +70,8 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange {

private lateinit var parsedHTMLTexts: List<ParsedHTMLText>
private lateinit var fastAdapter: FastAdapter<GenericItem>
private lateinit var emojiLoader: Deferred<EmojiCompat>
private lateinit var behavior: BottomSheetBehavior<LinearLayout>
private val upperAdsAdapter: ItemAdapter<Ads> = ItemAdapter()
private val lowerAdsAdapter: ItemAdapter<Ads> = ItemAdapter()
private val diseasesAdapter: ItemAdapter<Disease> = ItemAdapter()
Expand All @@ -70,50 +81,68 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange {
private val handwashingViewModel: HandwashingViewModel by activityViewModels()

init {
lifecycleScope.launch {
whenStarted {
loading.visibility = View.VISIBLE
informationViewModel.parsedHTMLText
.observe(viewLifecycleOwner, Observer {
if (it.isEmpty())
return@Observer
parsedHTMLTexts = it
upperAdsAdapter.add(Ads())
lowerAdsAdapter.add(Ads())
it.forEachIndexed { i, parsedText ->
val animation =
if (i % 2 == 0) R.raw.virus_red
else R.raw.virus_loader
val layoutId =
if (i % 2 == 0) R.layout.disease_card_layout
else R.layout.disease_card_alt_layout
diseasesAdapter.add(
Disease(animation, parsedText, layoutId, i)
)
}
loading.visibility = View.INVISIBLE
container.visibility = View.VISIBLE
})
handwashingViewModel.allData.observe(viewLifecycleOwner) {
lifecycleScope.launchWhenStarted {
loading.visibility = View.VISIBLE
countLoader.visibility = View.VISIBLE
informationViewModel.parsedHTMLText.observe(viewLifecycleOwner) {
Timber.d("Parsed HTML text changed - $it | ${it.isEmpty()}")
if (it.isEmpty())
return@observe
parsedHTMLTexts = it
upperAdsAdapter.add(Ads())
lowerAdsAdapter.add(Ads())
it.forEachIndexed { i, parsedText ->
val animation =
if (i % 2 == 0) R.raw.virus_red
else R.raw.virus_loader
val layoutId =
if (i % 2 == 0) R.layout.disease_card_layout
else R.layout.disease_card_alt_layout
diseasesAdapter.add(
Disease(animation, parsedText, layoutId, i)
)
}
loading.visibility = View.INVISIBLE
container.visibility = View.VISIBLE
}
handwashingViewModel.allData.observe(viewLifecycleOwner) {
lifecycleScope.launch {
val dataSet = BarDataSet(it.toBarEntry(), "label")
countChart.data = BarData(dataSet)
countChart.notifyDataSetChanged()
countChart.setVisibleXRangeMaximum(7F)
countChart.moveViewToX(0F)
val todayAmount =
handwashingViewModel.getAsync(CalendarUtils.today.time)
lifecycleScope.launch {
val count = todayAmount.await()?.amount ?: return@launch
countDailyTextView.text =
"Today you washed your hands $count times"
}
val weeklyAmount =
handwashingViewModel.getWeeklyCountAsync()
val monthlyAmount =
handwashingViewModel.getMonthlyCountAsync()
setCountText(
countDailyTextView,
R.string.today_washed,
todayAmount.await()?.amount ?: 0
)
setCountText(
countWeeklyTextView,
R.string.week_washed,
weeklyAmount.await()
)
setCountText(
countMonthlyTextView,
R.string.month_washed,
monthlyAmount.await()
)
countLoader.visibility = View.INVISIBLE
scrollView.visibility = View.VISIBLE
}
}
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
emojiLoader = EmojiLoader.loadAsync(view.context)
val adapters = listOf(upperAdsAdapter, diseasesAdapter, lowerAdsAdapter)
fastAdapter = FastAdapter.with(adapters)
val rvManager = LinearLayoutManager(context)
Expand All @@ -123,11 +152,16 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange {
}
fastAdapter.addEventHook(DiseaseClickEventHook())
fastAdapter.withSavedInstanceState(savedInstanceState)
behavior = BottomSheetBehavior.from(view.contentLayout)
view.countChart.setDrawGridBackground(false)
view.countChart.axisLeft.setDrawGridLines(false)
view.countChart.axisRight.setDrawGridLines(false)
view.countChart.xAxis.setDrawGridLines(false)
view.countChart.invalidate()
ViewCompat.setElevation(
view.contentLayout,
resources.getDimension(R.dimen.menu_elevation)
)
view.countUpButton.setOnClickListener {
lifecycleScope.launch {
val createdItem =
Expand All @@ -141,6 +175,36 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange {
)
)
handwashingViewModel.increment(CalendarUtils.today.time)
leaves.visibility = View.VISIBLE
if (!leaves.isAnimating)
leaves.playAnimation()
}
}
view.countDownButton.setOnClickListener {
lifecycleScope.launch {
val createdItem =
handwashingViewModel.getAsync(CalendarUtils.today.time)
.await()
if (createdItem == null)
handwashingViewModel.create(
Handwashing(
CalendarUtils.today.time,
0
)
)
handwashingViewModel.decrement(CalendarUtils.today.time)
}
}
lifecycleScope.launch {
val countUpText = getText(R.string.add_another)
val countDownText = getText(R.string.reduce_count)
val emojiCompat = emojiLoader.await()
try {
countUpButton.text = emojiCompat.process(countUpText)
countDownButton.text = emojiCompat.process(countDownText)
} catch (_: IllegalStateException) {
countUpButton.text = countUpText
countDownButton.text = countDownText
}
}
}
Expand All @@ -151,6 +215,12 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange {
}

fun onBackPressed() {
if (::behavior.isInitialized) {
if (behavior.state == STATE_EXPANDED) {
behavior.state = STATE_COLLAPSED
return
}
}
try {
container.adapter = null
diseasesAdapter.clear()
Expand All @@ -166,6 +236,23 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange {
lifecycleScope.launchWhenCreated { informationViewModel.parseHtml() }
}

private suspend fun setCountText(
view: MaterialTextView,
@StringRes id: Int,
times: Int
) {
val emojiCompat = emojiLoader.await()
val text = getString(
id,
resources.getQuantityString(R.plurals.times, times, times)
)
view.text = try {
emojiCompat.process(text)
} catch (_: IllegalStateException) {
text
}
}

private inner class DiseaseClickEventHook : ClickEventHook<Disease>() {
override fun onBind(viewHolder: RecyclerView.ViewHolder) =
if (viewHolder is Disease.ViewHolder) viewHolder.cardContainer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import com.javinator9889.handwashingreminder.utils.notNull
import kotlinx.coroutines.*
import org.sufficientlysecure.htmltextview.HtmlFormatter
import org.sufficientlysecure.htmltextview.HtmlFormatterBuilder
import timber.log.Timber

private const val DATA_KEY = "text:html:text"
private const val PARSED_JSON_KEY = "text:json:parsed"
Expand All @@ -57,49 +58,48 @@ class DiseaseInformationViewModel(
?: DiseasesList(emptyList())
}

fun parseHtml() {
viewModelScope.launch {
if (!state.get<List<ParsedHTMLText>>(DATA_KEY).isNullOrEmpty())
return@launch
val parsedItemsList =
ArrayList<ParsedHTMLText>(informationList.diseases.size)
val deferreds = mutableListOf<Collection<Deferred<Spanned>>>()
informationList.diseases.forEach { disease ->
deferreds.add(
listOf(
async { createHTML(disease.name) },
async { createHTML(disease.shortDescription) },
async { createHTML(disease.longDescription) },
async { createHTML(disease.provider) },
async { createHTML(disease.website) },
async { createHTML(disease.symptoms) },
async { createHTML(disease.prevention) }
)
fun parseHtml() = viewModelScope.launch {
Timber.d("Parsing HTML")
if (!state.get<List<ParsedHTMLText>>(DATA_KEY).isNullOrEmpty())
return@launch
val parsedItemsList =
ArrayList<ParsedHTMLText>(informationList.diseases.size)
val deferreds = mutableListOf<Collection<Deferred<Spanned>>>()
informationList.diseases.forEach { disease ->
deferreds.add(
listOf(
async { createHTML(disease.name) },
async { createHTML(disease.shortDescription) },
async { createHTML(disease.longDescription) },
async { createHTML(disease.provider) },
async { createHTML(disease.website) },
async { createHTML(disease.symptoms) },
async { createHTML(disease.prevention) }
)
}
deferreds.forEachIndexed { i, htmlData ->
launch {
val data = htmlData.awaitAll()
parsedItemsList.add(
i, ParsedHTMLText(
name = data[0],
shortDescription = data[1],
longDescription = data[2],
provider = data[3],
website = data[4],
symptoms = data[5],
prevention = data[6]
)
)
}
deferreds.forEachIndexed { i, htmlData ->
launch {
val data = htmlData.awaitAll()
parsedItemsList.add(
i, ParsedHTMLText(
name = data[0],
shortDescription = data[1],
longDescription = data[2],
provider = data[3],
website = data[4],
symptoms = data[5],
prevention = data[6]
)
withContext(Dispatchers.Main) {
)
withContext(Dispatchers.Main) {
state[DATA_KEY] = parsedItemsList
}
}.invokeOnCompletion {
it.notNull {
viewModelScope.launch(context = Dispatchers.Main) {
state[DATA_KEY] = parsedItemsList
}
}.invokeOnCompletion {
it.notNull {
viewModelScope.launch(context = Dispatchers.Main) {
state[DATA_KEY] = parsedItemsList
parsedHTMLText.value = parsedItemsList
}
parsedHTMLText.value = parsedItemsList
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ interface HandwashingDao {
@Query("UPDATE handwashing SET amount = amount + 1 WHERE date == :date")
suspend fun increment(date: Date)

@Query("UPDATE handwashing SET amount = amount - 1 WHERE date == :date")
@Query("UPDATE handwashing SET amount = CASE WHEN (amount == 0) THEN 0 ELSE amount - 1 END WHERE date == :date")
suspend fun decrement(date: Date)

@Query("DELETE FROM handwashing WHERE date == :date")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class HandwashingViewModel(application: Application) :
fun update(handwashing: Handwashing) =
viewModelScope.launch(Dispatchers.IO) { repository.update(handwashing) }

fun getAsync(date: Date) =
fun getAsync(date: Date = CalendarUtils.today.time) =
viewModelScope.async(Dispatchers.IO) { repository.get(date) }

fun getBetweenAsync(from: Date, to: Date) =
Expand All @@ -71,6 +71,10 @@ class HandwashingViewModel(application: Application) :
to = CalendarUtils.today.time
)

fun getTodayCountAsync() = viewModelScope.async {
return@async getAsync().await()?.amount
}

fun getWeeklyCountAsync() = viewModelScope.async {
val weeklyData = getWeeklyAsync().await()
var amount = 0
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/ic_calendar_month.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M20,3h-1L19,1h-2v2L7,3L7,1L5,1v2L4,3c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,5c0,-1.1 -0.9,-2 -2,-2zM20,21L4,21L4,8h16v13z"
android:fillColor="#000000"/>
</vector>
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/ic_calendar_today.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M19,3h-1L18,1h-2v2L8,3L8,1L6,1v2L5,3c-1.11,0 -1.99,0.9 -1.99,2L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19L5,8h14v11zM7,10h5v5L7,15z"
android:fillColor="#000000"/>
</vector>

0 comments on commit 3222c4e

Please sign in to comment.