Skip to content

Commit

Permalink
Fix: Dashboard glitch (#612)
Browse files Browse the repository at this point in the history
## Proposed Changes

  - Replace `HeterogeneousRecyclerAdapter` with `RecyclerView.Adapter`
  - Add a `ViewModel` for state management
  • Loading branch information
aanorbel authored Dec 5, 2023
1 parent e1b88e9 commit 87e854c
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 141 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/archive.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ jobs:
uses: actions/upload-artifact@v3
with:
name: dev-apk
path: app/build/outputs/apk/devFull/release
path: app/build/outputs/apk/devFull/release
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.openobservatory.ooniprobe.adapters

import android.content.res.Resources
import android.graphics.PorterDuff
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView
import org.openobservatory.ooniprobe.R
import org.openobservatory.ooniprobe.common.PreferenceManager
import org.openobservatory.ooniprobe.databinding.ItemSeperatorBinding
import org.openobservatory.ooniprobe.databinding.ItemTestsuiteBinding
import org.openobservatory.ooniprobe.test.suite.AbstractSuite

class DashboardAdapter(
private val items: List<Any>,
private val onClickListener: View.OnClickListener,
private val preferenceManager: PreferenceManager,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

companion object {
private const val VIEW_TYPE_TITLE = 0
private const val VIEW_TYPE_CARD = 1
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
VIEW_TYPE_TITLE -> {
CardGroupTitleViewHolder(
ItemSeperatorBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
)
}

else -> {
CardViewHolder(
ItemTestsuiteBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
)
}
}
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = items[position]
when (holder.itemViewType) {
VIEW_TYPE_TITLE -> {
}

VIEW_TYPE_CARD -> {
val cardHolder = holder as CardViewHolder
if (item is AbstractSuite) {
cardHolder.binding.apply {
title.setText(item.title)
desc.setText(item.cardDesc)
icon.setImageResource(item.iconGradient)
}
holder.itemView.tag = item
if (item.isTestEmpty(preferenceManager)) {
holder.setIsRecyclable(false)
holder.itemView.apply {
elevation = 0f
isClickable = false
}
val resources: Resources = holder.itemView.context.resources
(holder.itemView as CardView).setCardBackgroundColor(resources.getColor(R.color.disabled_test_background))
holder.binding.apply {
title.setTextColor(resources.getColor(R.color.disabled_test_text))
desc.setTextColor(resources.getColor(R.color.disabled_test_text))
icon.setColorFilter(resources.getColor(R.color.disabled_test_text), PorterDuff.Mode.SRC_IN)
}
} else {
holder.itemView.setOnClickListener(onClickListener)
}
}
}
}
}

override fun getItemCount(): Int {
return items.size
}

override fun getItemViewType(position: Int): Int {
return when (items[position]) {
is String -> VIEW_TYPE_TITLE
else -> VIEW_TYPE_CARD
}
}

/**
* ViewHolder for dashboard item group
* @param binding
*/
class CardGroupTitleViewHolder(var binding: ItemSeperatorBinding) : RecyclerView.ViewHolder(binding.root)

/**
* ViewHolder for dashboard item
* @param binding
*/
class CardViewHolder(var binding: ItemTestsuiteBinding) : RecyclerView.ViewHolder(binding.root)
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package org.openobservatory.ooniprobe.fragment

import android.os.Bundle
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import org.openobservatory.ooniprobe.R
import org.openobservatory.ooniprobe.activity.AbstractActivity
import org.openobservatory.ooniprobe.activity.OverviewActivity
import org.openobservatory.ooniprobe.activity.RunningActivity
import org.openobservatory.ooniprobe.adapters.DashboardAdapter
import org.openobservatory.ooniprobe.common.Application
import org.openobservatory.ooniprobe.common.PreferenceManager
import org.openobservatory.ooniprobe.common.ReachabilityManager
import org.openobservatory.ooniprobe.common.ThirdPartyServices
import org.openobservatory.ooniprobe.databinding.FragmentDashboardBinding
import org.openobservatory.ooniprobe.fragment.dashboard.DashboardViewModel
import org.openobservatory.ooniprobe.model.database.Result
import org.openobservatory.ooniprobe.test.suite.AbstractSuite
import javax.inject.Inject

class DashboardFragment : Fragment(), View.OnClickListener {
@Inject
lateinit var preferenceManager: PreferenceManager

@Inject
lateinit var viewModel: DashboardViewModel
private var testSuites: ArrayList<AbstractSuite> = ArrayList()
private lateinit var binding: FragmentDashboardBinding

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentDashboardBinding.inflate(inflater, container, false)
(requireActivity().application as Application).fragmentComponent.inject(this)
(requireActivity() as AppCompatActivity).apply {
setSupportActionBar(binding.toolbar)
supportActionBar?.title = null
}
binding.apply {
runAll.setOnClickListener { _: View? -> runAll() }
vpn.setOnClickListener { _: View? -> (requireActivity().application as Application).openVPNSettings() }
}
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.getGroupedItemList().observe(viewLifecycleOwner) { items ->
binding.recycler.layoutManager = LinearLayoutManager(requireContext())
binding.recycler.adapter = DashboardAdapter(items, this, preferenceManager)
}

viewModel.items.observe(viewLifecycleOwner) { items ->
testSuites.apply {
clear()
addAll(items)
}
}
}

override fun onResume() {
super.onResume()
setLastTest()
if (ReachabilityManager.isVPNinUse(this.context)
&& preferenceManager.isWarnVPNInUse
) binding.vpn.visibility = View.VISIBLE else binding.vpn.visibility = View.GONE
}

private fun setLastTest() {
val lastResult = Result.getLastResult()
if (lastResult == null) {
(getString(R.string.Dashboard_Overview_LatestTest) + " " + getString(R.string.Dashboard_Overview_LastRun_Never))
.also { binding.lastTested.text = it }
} else {
(getString(R.string.Dashboard_Overview_LatestTest) + " " + DateUtils.getRelativeTimeSpanString(lastResult.start_time.time))
.also { binding.lastTested.text = it }
}
}

private fun runAll() {
RunningActivity.runAsForegroundService(
activity as AbstractActivity?,
testSuites,
{ onTestServiceStartedListener() },
preferenceManager
)
}

private fun onTestServiceStartedListener() = try {
(requireActivity() as AbstractActivity).bindTestService()
} catch (e: Exception) {
e.printStackTrace()
ThirdPartyServices.logException(e)
}

override fun onClick(v: View) {
val testSuite = v.tag as AbstractSuite
when (v.id) {
R.id.run -> RunningActivity.runAsForegroundService(
activity as AbstractActivity?,
testSuite.asArray(), { onTestServiceStartedListener() },
preferenceManager
)

else -> ActivityCompat.startActivity(
requireActivity(),
OverviewActivity.newIntent(activity, testSuite),
null
)
}
}
}
Loading

0 comments on commit 87e854c

Please sign in to comment.