Skip to content

Commit

Permalink
Updated views to match various actions
Browse files Browse the repository at this point in the history
  • Loading branch information
aanorbel committed Jan 19, 2024
1 parent e543532 commit 0be1854
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,36 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.BaseExpandableListAdapter
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.google.android.material.checkbox.MaterialCheckBox
import com.google.gson.Gson
import com.google.gson.internal.LinkedTreeMap
import org.openobservatory.engine.BaseNettest
import org.openobservatory.ooniprobe.R
import org.openobservatory.ooniprobe.activity.AbstractActivity
import org.openobservatory.ooniprobe.common.PreferenceManager
import org.openobservatory.ooniprobe.common.TestDescriptorManager
import org.openobservatory.ooniprobe.databinding.ActivityReviewDescriptorUpdatesBinding
import org.openobservatory.ooniprobe.databinding.FragmentDescriptorUpdateBinding
import org.openobservatory.ooniprobe.model.database.ITestDescriptor
import org.openobservatory.ooniprobe.model.database.InstalledDescriptor
import org.openobservatory.ooniprobe.model.database.TestDescriptor
import org.openobservatory.ooniprobe.test.test.AbstractTest
import java.text.SimpleDateFormat
import java.util.Locale
import javax.inject.Inject

/**
* This activity is used to review the updates of the descriptors.
* When a new update is available, the user is prompted to review the changes.
* This activity is started by the [org.openobservatory.ooniprobe.activity.MainActivity] activity.
*/
class ReviewDescriptorUpdatesActivity : AbstractActivity() {

companion object {
Expand All @@ -47,9 +60,6 @@ class ReviewDescriptorUpdatesActivity : AbstractActivity() {
}
}

@Inject
lateinit var preferenceManager: PreferenceManager

@Inject
lateinit var descriptorManager: TestDescriptorManager

Expand All @@ -69,25 +79,40 @@ class ReviewDescriptorUpdatesActivity : AbstractActivity() {
supportActionBar?.title = "Link Update"
val descriptorJson = intent.getStringExtra(DESCRIPTORS)
try {
val descriptors: Array<TestDescriptor> =
gson.fromJson(descriptorJson, Array<TestDescriptor>::class.java)
/**
* **[descriptorJson]** is the json string of the intent.
* **[descriptors]** is the list of [TestDescriptor] objects obtained from **[descriptorJson]**.
* Because [TestDescriptor.nettests] is of type [Any], the gson library converts it to a [LinkedTreeMap].
*/
val descriptors: List<TestDescriptor> =
gson.fromJson(descriptorJson, Array<ITestDescriptor>::class.java)
.map { it.toTestDescriptor() }

// Disable swipe behavior of viewpager
binding.viewpager.isUserInputEnabled = false
reviewUpdatesPagingAdapter = ReviewUpdatesPagingAdapter(this, descriptors.toList())
binding.viewpager.adapter = reviewUpdatesPagingAdapter

reviewUpdatesPagingAdapter = ReviewUpdatesPagingAdapter(this, descriptors)
binding.viewpager.adapter = reviewUpdatesPagingAdapter

/**
* The bottom bar menu item click listener.
* When the user clicks on the update button, the viewpager is swiped to the next page.
* When the user clicks on the last update, the activity is finished.
*/
val bottomBarOnMenuItemClickListener: Toolbar.OnMenuItemClickListener =
Toolbar.OnMenuItemClickListener { item ->
when (item.itemId) {
R.id.update_descriptor -> {
descriptorManager.updateFromNetwork(descriptors[binding.viewpager.currentItem])
/**
* **[currPos]** is the current position of the viewpager.
* If the current position is not the last position, the viewpager is swiped to the next page.
* If the current position is the last position, the last update is saved in the shared preferences and the activity is finished.
*/
val currPos: Int = binding.viewpager.currentItem
if ((currPos + 1) != binding.viewpager.adapter?.itemCount) {
binding.viewpager.currentItem = currPos + 1
} else {
/*preferenceManager.setLastUpdateDescriptorReview(
descriptorManager.getDescriptorVersion()
)*/
finish()
}
true
Expand All @@ -98,10 +123,22 @@ class ReviewDescriptorUpdatesActivity : AbstractActivity() {
}
binding.bottomBar.setOnMenuItemClickListener(bottomBarOnMenuItemClickListener)

/**
* The viewpager page change callback.
* When the user swipes to the next page, the bottom bar menu item title is updated.
*/
binding.viewpager.registerOnPageChangeCallback(object : OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
binding.bottomBar.menu.findItem(R.id.update_descriptor)
?.setTitle("UPDATE (${position + 1} of ${binding.viewpager.adapter?.itemCount})")
?.let {
val countString =
"(${position + 1} of ${binding.viewpager.adapter?.itemCount})"
it.title = if ((position + 1) != binding.viewpager.adapter?.itemCount) {
"UPDATE $countString"
} else {
"UPDATE AND FINISH $countString"
}
}

}
})
Expand Down Expand Up @@ -129,6 +166,11 @@ class ReviewDescriptorUpdatesActivity : AbstractActivity() {
}
}

/**
* This adapter is used to display the list of descriptors in the viewpager.
* @param fragmentActivity is the activity that contains the viewpager.
* @param descriptors is the list of descriptors to display.
*/
class ReviewUpdatesPagingAdapter(
fragmentActivity: FragmentActivity,
private val descriptors: List<TestDescriptor>
Expand All @@ -146,6 +188,11 @@ class ReviewUpdatesPagingAdapter(

private const val DESCRIPTOR = "descriptor"

/**
* This fragment is used to display the details of a descriptor.
* It is used by [ReviewUpdatesPagingAdapter].
* @param descriptor is the descriptor to display.
*/
class DescriptorUpdateFragment : Fragment() {

private lateinit var binding: FragmentDescriptorUpdateBinding
Expand All @@ -169,9 +216,143 @@ class DescriptorUpdateFragment : Fragment() {
val absDescriptor = InstalledDescriptor(descriptor)
binding.apply {
title.text = absDescriptor.title
author.text = "Created by ${descriptor.author} on ${
SimpleDateFormat(
"MMM dd, yyyy",
Locale.getDefault()
).format(descriptor.descriptorCreationTime)
}"
description.text = absDescriptor.description // Use markdown
icon.setImageResource(absDescriptor.getDisplayIcon(requireContext()))
val adapter =
ReviewDescriptorExpandableListAdapter(nettests = absDescriptor.nettests)
expandableListView.setAdapter(adapter)
// Expand all groups
for (i in 0 until adapter.groupCount) {
expandableListView.expandGroup(i)
}
}
}
}
}

/**
* This adapter is used to display the list of nettests in the expandable list view.
* It is used by [DescriptorUpdateFragment] to display the list of nettests.
* @param nettests is the list of nettests to display.
*/
class ReviewDescriptorExpandableListAdapter(
val nettests: List<BaseNettest>,
) : BaseExpandableListAdapter() {

/**
* @return Number of groups in the list.
*/
override fun getGroupCount(): Int = nettests.size

/**
* @param groupPosition Position of the group in the list.
* @return Number of children in the group.
*/
override fun getChildrenCount(groupPosition: Int): Int =
nettests[groupPosition].inputs?.size ?: 0

/**
* @param groupPosition Position of the group in the list.
* @return [BaseNettest] object.
*/
override fun getGroup(groupPosition: Int): BaseNettest = nettests[groupPosition]

/**
* @param groupPosition Position of the group in the list.
* @param childPosition Position of the child in the group.
* @return string item at position.
*/
override fun getChild(groupPosition: Int, childPosition: Int): String? =
nettests[groupPosition].inputs?.get(childPosition)

/**
* @param groupPosition Position of the group in the list.
* @return Group position.
*/
override fun getGroupId(groupPosition: Int): Long = groupPosition.toLong()

/**
* @param groupPosition Position of the group in the list.
* @param childPosition Position of the child in the group.
* @return Child position.
*/
override fun getChildId(groupPosition: Int, childPosition: Int): Long = childPosition.toLong()

/**
* @return true if the same ID always refers to the same object.
*/
override fun hasStableIds(): Boolean = false

/**
* @param groupPosition Position of the group in the list.
* @param isExpanded true if the group is expanded.
* @param convertView View of the group.
* @param parent Parent view.
* @return View of the group.
*/
override fun getGroupView(
groupPosition: Int,
isExpanded: Boolean,
convertView: View?,
parent: ViewGroup,
): View {
val view = convertView ?: LayoutInflater.from(parent.context)
.inflate(R.layout.nettest_group_list_item, parent, false)
val groupItem = getGroup(groupPosition)
val groupIndicator = view.findViewById<ImageView>(R.id.group_indicator)

val abstractNettest = AbstractTest.getTestByName(groupItem.name)
view.findViewById<TextView>(R.id.group_name).text =
when (abstractNettest.labelResId == R.string.Test_Experimental_Fullname) {
true -> groupItem.name
false -> parent.context.resources.getText(abstractNettest.labelResId)
}

val groupCheckBox = view.findViewById<MaterialCheckBox>(R.id.groupCheckBox)
groupCheckBox.visibility = View.GONE
if (groupItem.inputs?.isNotEmpty() == true) {
if (isExpanded) {
groupIndicator.setImageResource(R.drawable.expand_less)
} else {
groupIndicator.setImageResource(R.drawable.expand_more)
}
} else {
groupIndicator.visibility = View.INVISIBLE
}

return view
}

/**
* @param groupPosition Position of the group in the list.
* @param childPosition Position of the child in the group.
* @param isLastChild True if the child is the last child in the group.
* @param convertView View object.
* @param parent ViewGroup object.
* @return View object.
*/
override fun getChildView(
groupPosition: Int,
childPosition: Int,
isLastChild: Boolean,
convertView: View?,
parent: ViewGroup
): View {
val view = convertView ?: LayoutInflater.from(parent.context)
.inflate(R.layout.nettest_child_list_item, parent, false)

view.findViewById<TextView>(R.id.text).apply {
text = getChild(groupPosition, childPosition)
}
return view
}

override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean = false

}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ class TestDescriptorManager @Inject constructor(
return descriptor.save()
}

fun getById(runId: Long): TestDescriptor? {
return SQLite.select().from(TestDescriptor::class.java)
.where(TestDescriptor_Table.runId.eq(runId)).querySingle()
}

fun getRunV2Descriptors(): List<TestDescriptor> {
return SQLite.select().from(TestDescriptor::class.java)
.where(TestDescriptor_Table.isArchived.eq(false)).queryList()
Expand Down Expand Up @@ -127,4 +132,13 @@ class TestDescriptorManager @Inject constructor(
return SQLite.select().from(TestDescriptor::class.java)
.where(TestDescriptor_Table.auto_update.eq(false)).queryList()
}

fun updateFromNetwork(testDescriptor: TestDescriptor): Boolean {
getById(testDescriptor.runId)?.let { descriptor ->
testDescriptor.isAutoUpdate = descriptor.isAutoUpdate
return testDescriptor.save()
} ?: run {
return false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,61 @@ class NettestConverter : TypeConverter<String, Any>() {
).toList()
}

class ITestDescriptor(

var runId: Long = 0,

var name: String = "",

var nameIntl: HashMap<String, String>? = null,

var author: String = "",

var shortDescription: String = "",

var shortDescriptionIntl: HashMap<String, String>? = null,

var description: String = "",

var descriptionIntl: HashMap<String, String>? = null,

var icon: String? = null,

var color: String? = null,

var animation: String? = null,

var isArchived: Boolean = false,

var isAutoRun: Boolean = true,

var isAutoUpdate: Boolean = false,

var descriptorCreationTime: Date? = null,

var translationCreationTime: Date? = null,

var nettests: List<OONIRunNettest>? = emptyList()
): Serializable {
fun toTestDescriptor(): TestDescriptor {
return TestDescriptor(
runId = runId,
name = name,
nameIntl = nameIntl,
author = author,
shortDescription = shortDescription,
shortDescriptionIntl = shortDescriptionIntl,
description = description,
descriptionIntl = descriptionIntl,
icon = icon,
color = color,
animation = animation,
isArchived = isArchived,
isAutoRun = isAutoRun,
isAutoUpdate = isAutoUpdate,
descriptorCreationTime = descriptorCreationTime,
translationCreationTime = translationCreationTime,
nettests = nettests ?: emptyList<OONIRunNettest>()
)
}
}
3 changes: 2 additions & 1 deletion app/src/main/res/layout/fragment_descriptor_update.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@
android:id="@+id/tests_llabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tests"
android:text="UPDATES"
android:textFontWeight="600"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/description" />

Expand Down

0 comments on commit 0be1854

Please sign in to comment.