diff --git a/app/build.gradle b/app/build.gradle index b1df3e7a9..f5bb70ff1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,6 +18,7 @@ android { testInstrumentationRunner "org.openobservatory.ooniprobe.TestAndroidJUnitRunner" buildConfigField 'String', 'OONI_API_BASE_URL', '"https://api.ooni.io/"' buildConfigField 'String', 'NOTIFICATION_SERVER', '"https://countly.ooni.io"' + buildConfigField 'String', 'OONI_RUN_DASHBOARD_URL', '"https://run.test.ooni.org"' resValue "string", "APP_ID", 'org.openobservatory.ooniprobe' resValue "string", "APP_NAME", "OONI Probe" buildConfigField 'String', 'SOFTWARE_NAME', 'BASE_SOFTWARE_NAME+IS_DEBUG' @@ -51,7 +52,8 @@ android { stable { dimension 'testing' buildConfigField 'String', 'BASE_SOFTWARE_NAME', '"ooniprobe-android"' - buildConfigField 'String', 'OONI_API_BASE_URL', '"https://api.dev.ooni.io"' + buildConfigField 'String', 'OONI_API_BASE_URL', '"https://api.prod.ooni.io/"' + buildConfigField 'String', 'OONI_RUN_DASHBOARD_URL', '"https://run-v2.ooni.org"' } dev { dimension 'testing' diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/MainActivity.java b/app/src/main/java/org/openobservatory/ooniprobe/activity/MainActivity.java index 7c2e5a475..b1397b5ff 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/MainActivity.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/MainActivity.java @@ -28,7 +28,7 @@ import com.google.android.material.snackbar.Snackbar; import org.openobservatory.ooniprobe.R; -import org.openobservatory.ooniprobe.activity.reviewdescriptorupdates.AvailableUpdatesViewModel; +import org.openobservatory.ooniprobe.common.AppUpdatesViewModel; import org.openobservatory.ooniprobe.activity.reviewdescriptorupdates.ReviewDescriptorUpdatesActivity; import org.openobservatory.ooniprobe.common.PreferenceManager; import org.openobservatory.ooniprobe.common.TestDescriptorManager; @@ -71,7 +71,7 @@ public class MainActivity extends ReviewUpdatesAbstractActivity implements Confi TestDescriptorManager descriptorManager; @Inject - AvailableUpdatesViewModel updatesViewModel; + AppUpdatesViewModel updatesViewModel; public static Intent newIntent(Context context, int resItem) { @@ -101,6 +101,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { getSupportFragmentManager().beginTransaction().replace(R.id.content, new DashboardFragment()).commit(); return true; case R.id.testResults: + updatesViewModel.getTestRunComplete().setValue(false); getSupportFragmentManager().beginTransaction().replace(R.id.content, new ResultListFragment()).commit(); return true; case R.id.settings: @@ -366,4 +367,8 @@ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent d } } } + + public void showResults() { + binding.bottomNavigation.setSelectedItemId(R.id.testResults); + } } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java b/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java index ed14e65c1..95c1fcf82 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java @@ -10,7 +10,6 @@ import android.content.Intent; import android.os.Bundle; import android.os.Parcelable; -import android.text.TextUtils; import android.view.View; import android.view.Window; import android.widget.ImageView; @@ -37,7 +36,7 @@ import org.openobservatory.ooniprobe.activity.overview.OverviewTestsExpandableListViewAdapter; import org.openobservatory.ooniprobe.activity.overview.OverviewViewModel; import org.openobservatory.ooniprobe.activity.overview.RevisionsFragment; -import org.openobservatory.ooniprobe.activity.reviewdescriptorupdates.AvailableUpdatesViewModel; +import org.openobservatory.ooniprobe.common.AppUpdatesViewModel; import org.openobservatory.ooniprobe.activity.reviewdescriptorupdates.ReviewDescriptorUpdatesActivity; import org.openobservatory.ooniprobe.common.AbstractDescriptor; import org.openobservatory.ooniprobe.common.OONITests; @@ -71,7 +70,7 @@ public class OverviewActivity extends ReviewUpdatesAbstractActivity implements C OverviewViewModel viewModel; @Inject - AvailableUpdatesViewModel updatesViewModel; + AppUpdatesViewModel updatesViewModel; @Inject TestDescriptorManager testDescriptorManager; diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/RunningActivity.java b/app/src/main/java/org/openobservatory/ooniprobe/activity/RunningActivity.java index 6e9fc341c..c696695f8 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/RunningActivity.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/RunningActivity.java @@ -176,7 +176,7 @@ private void applyUIChanges(RunTestService service) { binding.eta.setText(R.string.Dashboard_Running_CalculatingETA); } - if (Objects.equals(service.task.currentTest.getLabelResId(),R.string.Test_Experimental_Fullname)) { + if (Objects.equals(service.task.currentSuite.getName(),OONITests.EXPERIMENTAL.getLabel())) { binding.name.setText(service.task.currentTest.getName()); } else { binding.name.setText(getString(service.task.currentTest.getLabelResId())); @@ -293,8 +293,8 @@ public void onEnd(Context context) { } @NonNull - private static String readableTimeRemaining(double timeLeft) { + public static String readableTimeRemaining(double timeLeft) { long letaValue = Math.round(timeLeft); - return String.format(ENGLISH,"%dm %02ds", letaValue/60, letaValue%60); + return String.format(ENGLISH," %dm %02ds", letaValue/60, letaValue%60); } } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/adddescriptor/AddDescriptorActivity.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/adddescriptor/AddDescriptorActivity.kt index 5f424377a..99552ca87 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/adddescriptor/AddDescriptorActivity.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/adddescriptor/AddDescriptorActivity.kt @@ -159,8 +159,8 @@ class AddDescriptorActivity : AbstractActivity() { finish() } - viewModel.selectedAllBtnStatus.observe(this) { state -> - binding.testsCheckbox.checkedState = state; + binding.expandableListView.viewTreeObserver.addOnGlobalLayoutListener { + binding.testsCheckbox.checkedState = viewModel.selectedAllBtnStatus.value!! } // This observer is used to change the state of the "Select All" button when a checkbox is clicked. diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/oonirun/OoniRunV2Activity.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/oonirun/OoniRunV2Activity.kt index e832525af..f505d9ebc 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/oonirun/OoniRunV2Activity.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/oonirun/OoniRunV2Activity.kt @@ -133,6 +133,7 @@ class OoniRunV2Activity : AbstractActivity() { private fun fetchDescriptorComplete(descriptorResponse: TestDescriptor?) { descriptorResponse?.let { startActivity(AddDescriptorActivity.newIntent(this, descriptorResponse)) + finish() } ?: run { finishWithError() } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/overview/RevisionsView.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/overview/RevisionsView.kt index 09a37fcf2..aeb425846 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/overview/RevisionsView.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/overview/RevisionsView.kt @@ -13,6 +13,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.gson.Gson import org.openobservatory.engine.OONIRunRevisions +import org.openobservatory.ooniprobe.BuildConfig import org.openobservatory.ooniprobe.R import org.openobservatory.ooniprobe.databinding.FragmentRevisionsBinding import org.openobservatory.ooniprobe.databinding.ItemTextBinding @@ -63,14 +64,15 @@ class RevisionsFragment : Fragment() { with(binding.list) { layoutManager = LinearLayoutManager(context) - adapter = revisions?.revisions?.let { + adapter = revisions?.revisions?.take(5)?.let { RevisionsRecyclerViewAdapter(it, object : OnItemClickListener { override fun onItemClick(position: Int) { startActivity( Intent( Intent.ACTION_VIEW, Uri.parse( - "https://run.test.ooni.org/revisions/%s?revision=%s".format( + "%s/revisions/%s?revision=%s".format( + BuildConfig.OONI_RUN_DASHBOARD_URL, runId, it[position] ) diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/reviewdescriptorupdates/ReviewDescriptorUpdatesActivity.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/reviewdescriptorupdates/ReviewDescriptorUpdatesActivity.kt index 6a47df898..267ad3da7 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/reviewdescriptorupdates/ReviewDescriptorUpdatesActivity.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/reviewdescriptorupdates/ReviewDescriptorUpdatesActivity.kt @@ -13,7 +13,6 @@ 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 @@ -24,6 +23,7 @@ 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.AppUpdatesViewModel import org.openobservatory.ooniprobe.common.TestDescriptorManager import org.openobservatory.ooniprobe.databinding.ActivityReviewDescriptorUpdatesBinding import org.openobservatory.ooniprobe.databinding.FragmentDescriptorUpdateBinding @@ -79,7 +79,7 @@ class ReviewDescriptorUpdatesActivity : AbstractActivity() { lateinit var gson: Gson @Inject - lateinit var updatesViewModel: AvailableUpdatesViewModel + lateinit var updatesViewModel: AppUpdatesViewModel private lateinit var reviewUpdatesPagingAdapter: ReviewUpdatesPagingAdapter diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/RunTestsActivity.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/RunTestsActivity.kt index 22d6e603d..56a433bfe 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/RunTestsActivity.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/RunTestsActivity.kt @@ -18,6 +18,7 @@ import org.openobservatory.ooniprobe.activity.runtests.adapter.RunTestsExpandabl import org.openobservatory.ooniprobe.activity.runtests.models.ChildItem import org.openobservatory.ooniprobe.activity.runtests.models.GroupItem import org.openobservatory.ooniprobe.common.AbstractDescriptor +import org.openobservatory.ooniprobe.common.OONIDescriptor import org.openobservatory.ooniprobe.common.OONITests import org.openobservatory.ooniprobe.common.PreferenceManager import org.openobservatory.ooniprobe.common.disableTest @@ -82,10 +83,20 @@ class RunTestsActivity : AbstractActivity() { intent.extras?.getSerializable(TESTS) as List>? descriptors?.let { _descriptors -> - adapter = RunTestsExpandableListViewAdapter( - _descriptors.map { descriptor -> + val groupedItemList = mutableListOf() + + _descriptors.groupBy { it.javaClass }.forEach { (type, itemList) -> + if (type == OONIDescriptor::class.java){ + groupedItemList.add("OONI Tests".uppercase()) + } else { + groupedItemList.add("OONI RUN Links".uppercase()) + } + groupedItemList.addAll(itemList.map { descriptor -> descriptor.toRunTestsGroupItem(preferenceManager = preferenceManager) - }, + }) + } + adapter = RunTestsExpandableListViewAdapter( + groupedItemList, viewModel ) @@ -137,22 +148,25 @@ class RunTestsActivity : AbstractActivity() { */ private fun updatePreferences() { for (i in 0 until adapter.groupCount) { - val group = adapter.getGroup(i) - when (group.name) { - OONITests.EXPERIMENTAL.label -> { - val testNames = OONITests.EXPERIMENTAL.nettests.map { it.name }; - when(group.nettests.filter { testNames.contains(it.name) }.map { it.selected }.all { it }) { - true -> preferenceManager.enableTest(OONITests.EXPERIMENTAL.label) - false -> preferenceManager.disableTest(OONITests.EXPERIMENTAL.label) - } - } - else -> group.nettests.forEach { nettest -> - when(nettest.selected) { - true -> preferenceManager.enableTest(nettest.name, group.preferencePrefix()) - false -> preferenceManager.disableTest(nettest.name, group.preferencePrefix()) - } - } - } + when(val group = adapter.getGroup(i)) { + is GroupItem ->{ + when (group.name) { + OONITests.EXPERIMENTAL.label -> { + val testNames = OONITests.EXPERIMENTAL.nettests.map { it.name }; + when(group.nettests.filter { testNames.contains(it.name) }.map { it.selected }.all { it }) { + true -> preferenceManager.enableTest(OONITests.EXPERIMENTAL.label) + false -> preferenceManager.disableTest(OONITests.EXPERIMENTAL.label) + } + } + else -> group.nettests.forEach { nettest -> + when(nettest.selected) { + true -> preferenceManager.enableTest(nettest.name, group.preferencePrefix()) + false -> preferenceManager.disableTest(nettest.name, group.preferencePrefix()) + } + } + } + } + } } } @@ -197,11 +211,15 @@ class RunTestsActivity : AbstractActivity() { private fun getChildItemsSelectedIdList(): List { val childItemSelectedIdList: MutableList = ArrayList() for (i in 0 until adapter.groupCount) { - val secondLevelItemList: List = adapter.getGroup(i).nettests - secondLevelItemList - .asSequence() - .filter { it.selected } - .mapTo(childItemSelectedIdList) { it.name } + when(val group = adapter.getGroup(i)) { + is GroupItem ->{ + val secondLevelItemList: List = group.nettests + secondLevelItemList + .asSequence() + .filter { it.selected } + .mapTo(childItemSelectedIdList) { it.name } + } + } } return childItemSelectedIdList } @@ -209,10 +227,14 @@ class RunTestsActivity : AbstractActivity() { private fun getGroupItemsAtLeastOneChildEnabled(): List { val items: MutableList = ArrayList() for (i in 0 until adapter.groupCount) { - if (adapter.getGroup(i).nettests.any { it.selected }) { - items.add(adapter.getGroup(i).apply { - nettests = nettests.filter { it.selected } - }) + when(val group = adapter.getGroup(i)) { + is GroupItem ->{ + if (group.nettests.any { it.selected }) { + items.add(group.apply { + nettests = nettests.filter { it.selected } + }) + } + } } } return items diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/adapter/RunTestsExpandableListViewAdapter.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/adapter/RunTestsExpandableListViewAdapter.kt index 5c94b71c3..f6b7d5e75 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/adapter/RunTestsExpandableListViewAdapter.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/runtests/adapter/RunTestsExpandableListViewAdapter.kt @@ -6,6 +6,7 @@ import android.view.ViewGroup import android.widget.BaseExpandableListAdapter import android.widget.ImageView import android.widget.TextView +import androidx.core.content.ContextCompat import org.openobservatory.ooniprobe.R import org.openobservatory.ooniprobe.activity.runtests.RunTestsViewModel import org.openobservatory.ooniprobe.activity.runtests.RunTestsViewModel.Companion.SELECT_ALL @@ -22,7 +23,7 @@ import org.openobservatory.ooniprobe.test.test.AbstractTest * @param viewModel RunTestsViewModel object. */ class RunTestsExpandableListViewAdapter( - private val groupedListData: List, + private val groupedListData: List, private val viewModel: RunTestsViewModel ) : BaseExpandableListAdapter() { /** @@ -36,21 +37,26 @@ class RunTestsExpandableListViewAdapter( * @param groupPosition Position of the group in the list. * @return Number of children in the group. */ - override fun getChildrenCount(groupPosition: Int): Int = groupedListData[groupPosition].nettests.size + override fun getChildrenCount(groupPosition: Int): Int = when (val group = groupedListData[groupPosition]) { + is GroupItem -> group.nettests.size + else -> 0 + } /** * @param groupPosition Position of the group in the list. * @return GroupItem object. */ - override fun getGroup(groupPosition: Int): GroupItem = groupedListData[groupPosition] + override fun getGroup(groupPosition: Int): Any = groupedListData[groupPosition] /** * @param groupPosition Position of the group in the list. * @param childPosition Position of the child in the group. * @return ChildItem object. */ - override fun getChild(groupPosition: Int, childPosition: Int): ChildItem = - groupedListData[groupPosition].nettests[childPosition] + override fun getChild(groupPosition: Int, childPosition: Int): ChildItem = when (val group = groupedListData[groupPosition]) { + is GroupItem -> group.nettests[childPosition] + else -> throw IllegalArgumentException("GroupItem expected") + } /** * @param groupPosition Position of the group in the list. @@ -78,68 +84,79 @@ class RunTestsExpandableListViewAdapter( * @return View object. */ override fun getGroupView(groupPosition: Int, isExpanded: Boolean, convertView: View?, parent: ViewGroup): View? { - var convertView = - convertView ?: LayoutInflater.from(parent.context).inflate(R.layout.run_tests_group_list_item, parent, false) + val groupItem = getGroup(groupPosition) - convertView.findViewById(R.id.group_name).text = groupItem.title - val icon = convertView.findViewById(R.id.group_icon) - icon.setImageResource(groupItem.getDisplayIcon(parent.context)) - icon.setColorFilter(groupItem.color) - val groupIndicator = convertView.findViewById(R.id.group_indicator) - val groupSelectionIndicator = convertView.findViewById(R.id.group_select_indicator) - val selectedAllBtnStatus = viewModel.selectedAllBtnStatus.getValue() - if (selectedAllBtnStatus == SELECT_ALL) { - groupItem.selected = true - for (childItem in groupItem.nettests) { - childItem.selected = true - } - } else if (selectedAllBtnStatus == SELECT_NONE) { - groupItem.selected = false - for (childItem in groupItem.nettests) { - childItem.selected = false - } - } else if (isSelectAllChildItems(groupItem.nettests)) { - groupItem.selected = true - } - if (groupItem.selected) { - if (isSelectAllChildItems(groupItem.nettests)) { - groupSelectionIndicator.setImageResource(R.drawable.check_box) - } else { - groupSelectionIndicator.setImageResource(R.drawable.check_box_outline_blank) - } - } else { - groupSelectionIndicator.setImageResource(R.drawable.check_box_outline_blank) - } - groupSelectionIndicator.setOnClickListener { - if (groupItem.selected && isSelectAllChildItems(groupItem.nettests)) { - groupItem.selected = false - for (childItem in groupItem.nettests) { - childItem.selected = false + + when(groupItem){ + is GroupItem -> { + var convertView = LayoutInflater.from(parent.context).inflate(R.layout.run_tests_group_list_item, parent, false) + convertView.findViewById(R.id.group_name).text = groupItem.title + val icon = convertView.findViewById(R.id.group_icon) + icon.setImageResource(groupItem.getDisplayIcon(parent.context)) + icon.setColorFilter(groupItem.color) + val groupSelectionIndicator = convertView.findViewById(R.id.group_select_indicator) + val selectedAllBtnStatus = viewModel.selectedAllBtnStatus.getValue() + if (selectedAllBtnStatus == SELECT_ALL) { + groupItem.selected = true + for (childItem in groupItem.nettests) { + childItem.selected = true + } + } else if (selectedAllBtnStatus == SELECT_NONE) { + groupItem.selected = false + for (childItem in groupItem.nettests) { + childItem.selected = false + } + } else if (isSelectAllChildItems(groupItem.nettests)) { + groupItem.selected = true } - if (isNotSelectedAnyGroupItem(groupedListData)) { - viewModel.setSelectedAllBtnStatus(SELECT_NONE) + if (groupItem.selected) { + if (isSelectAllChildItems(groupItem.nettests)) { + groupSelectionIndicator.setImageResource(R.drawable.check_box) + } else { + groupSelectionIndicator.setImageResource(R.drawable.check_box_outline_blank) + } } else { - viewModel.setSelectedAllBtnStatus(SELECT_SOME) + groupSelectionIndicator.setImageResource(R.drawable.check_box_outline_blank) } - } else { - groupItem.selected = true - for (childItem in groupItem.nettests) { - childItem.selected = true + groupSelectionIndicator.setOnClickListener { + if (groupItem.selected && isSelectAllChildItems(groupItem.nettests)) { + groupItem.selected = false + for (childItem in groupItem.nettests) { + childItem.selected = false + } + if (isNotSelectedAnyGroupItem(groupedListData)) { + viewModel.setSelectedAllBtnStatus(SELECT_NONE) + } else { + viewModel.setSelectedAllBtnStatus(SELECT_SOME) + } + } else { + groupItem.selected = true + for (childItem in groupItem.nettests) { + childItem.selected = true + } + if (isSelectedAllItems(groupedListData)) { + viewModel.setSelectedAllBtnStatus(SELECT_ALL) + } else { + viewModel.setSelectedAllBtnStatus(SELECT_SOME) + } + } + notifyDataSetChanged() } - if (isSelectedAllItems(groupedListData)) { - viewModel.setSelectedAllBtnStatus(SELECT_ALL) - } else { - viewModel.setSelectedAllBtnStatus(SELECT_SOME) + convertView.findViewById(R.id.group_indicator)?.let { groupIndicator -> + if (isExpanded) { + groupIndicator.setImageResource(R.drawable.expand_less) + } else { + groupIndicator.setImageResource(R.drawable.expand_more) + } + } + return convertView + } + else -> { + return LayoutInflater.from(parent.context).inflate(R.layout.run_tests_group_divider, parent, false).apply { + findViewById(R.id.name).text = groupItem.toString() } } - notifyDataSetChanged() - } - if (isExpanded) { - groupIndicator.setImageResource(R.drawable.expand_less) - } else { - groupIndicator.setImageResource(R.drawable.expand_more) } - return convertView } /** @@ -180,27 +197,31 @@ class RunTestsExpandableListViewAdapter( false -> R.drawable.check_box_outline_blank } ) - setOnClickListener { - if (childItem.selected) { - childItem.selected = false - if (isNotSelectedAnyChildItems(groupItem.nettests)) { - groupItem.selected = false - } - if (isNotSelectedAnyItems(groupedListData)) { - viewModel.setSelectedAllBtnStatus(SELECT_NONE) - } else { - viewModel.setSelectedAllBtnStatus(SELECT_SOME) - } - } else { - childItem.selected = true - groupItem.selected = true - if (isSelectedAllItems(groupedListData)) { - viewModel.setSelectedAllBtnStatus(SELECT_ALL) - } else { - viewModel.setSelectedAllBtnStatus(SELECT_SOME) + when (groupItem) { + is GroupItem -> { + setOnClickListener { + if (childItem.selected) { + childItem.selected = false + if (isNotSelectedAnyChildItems(groupItem.nettests)) { + groupItem.selected = false + } + if (isNotSelectedAnyItems(groupedListData)) { + viewModel.setSelectedAllBtnStatus(SELECT_NONE) + } else { + viewModel.setSelectedAllBtnStatus(SELECT_SOME) + } + } else { + childItem.selected = true + groupItem.selected = true + if (isSelectedAllItems(groupedListData)) { + viewModel.setSelectedAllBtnStatus(SELECT_ALL) + } else { + viewModel.setSelectedAllBtnStatus(SELECT_SOME) + } + } + notifyDataSetChanged() } } - notifyDataSetChanged() } } return convertView @@ -217,8 +238,8 @@ class RunTestsExpandableListViewAdapter( * @param groupItemsList List of GroupItem objects. * @return True if no group item in the list is selected. */ - private fun isNotSelectedAnyGroupItem(groupItemsList: List): Boolean { - for (groupItem in groupItemsList) { + private fun isNotSelectedAnyGroupItem(groupItemsList: List): Boolean { + for (groupItem in groupItemsList.filterIsInstance()) { if (groupItem.selected) { return false } @@ -256,8 +277,8 @@ class RunTestsExpandableListViewAdapter( * @param groupItemList List of GroupItem objects. * @return True if all group items in the list are selected. */ - private fun isSelectedAllItems(groupItemList: List?): Boolean { - for (groupItem in groupItemList!!) { + private fun isSelectedAllItems(groupItemList: List): Boolean { + for (groupItem in groupItemList.filterIsInstance()) { if (!groupItem.selected) { return false } @@ -272,8 +293,8 @@ class RunTestsExpandableListViewAdapter( * @param groupItemList List of GroupItem objects. * @return True if no group item in the list is selected. */ - private fun isNotSelectedAnyItems(groupItemList: List?): Boolean { - for (groupItem in groupItemList!!) { + private fun isNotSelectedAnyItems(groupItemList: List): Boolean { + for (groupItem in groupItemList.filterIsInstance()) { if (groupItem.selected) { return false } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/adapters/ResultDetailExpandableListAdapter.kt b/app/src/main/java/org/openobservatory/ooniprobe/adapters/ResultDetailExpandableListAdapter.kt index 61742dff4..4a25daea3 100755 --- a/app/src/main/java/org/openobservatory/ooniprobe/adapters/ResultDetailExpandableListAdapter.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/adapters/ResultDetailExpandableListAdapter.kt @@ -92,10 +92,15 @@ class ResultDetailExpandableListAdapter( else -> { val groupMeasurement = (items[groupPosition] as MeasurementGroup) + val groupTitle = root.findViewById(R.id.text) + groupMeasurement.measurements.firstOrNull()?.let { - root.findViewById(R.id.text).setText(it.getTest().labelResId) + when (it.getTest().labelResId){ + R.string.Test_Experimental_Fullname -> groupTitle.text = groupMeasurement.title + else -> groupTitle.setText(it.getTest().labelResId) + } } ?: run { - root.findViewById(R.id.text).text = groupMeasurement.title + groupTitle.text = groupMeasurement.title } root.findViewById(R.id.indicator).apply { visibility = View.VISIBLE diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/reviewdescriptorupdates/AvailableUpdatesViewModel.kt b/app/src/main/java/org/openobservatory/ooniprobe/common/AppUpdatesViewModel.kt similarity index 78% rename from app/src/main/java/org/openobservatory/ooniprobe/activity/reviewdescriptorupdates/AvailableUpdatesViewModel.kt rename to app/src/main/java/org/openobservatory/ooniprobe/common/AppUpdatesViewModel.kt index 34435682a..50bab2e09 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/reviewdescriptorupdates/AvailableUpdatesViewModel.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/common/AppUpdatesViewModel.kt @@ -1,4 +1,4 @@ -package org.openobservatory.ooniprobe.activity.reviewdescriptorupdates +package org.openobservatory.ooniprobe.common import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -8,8 +8,9 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class AvailableUpdatesViewModel @Inject() constructor(var gson: Gson) : ViewModel() { +class AppUpdatesViewModel @Inject() constructor(var gson: Gson) : ViewModel() { var descriptors: MutableLiveData> = MutableLiveData() + var testRunComplete: MutableLiveData = MutableLiveData() fun setDescriptorsWith(descriptorJson: String) { descriptors.value = gson.fromJson(descriptorJson, Array::class.java).toList() diff --git a/app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.kt b/app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.kt index 1173c409e..01099370c 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.kt @@ -7,6 +7,7 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import org.openobservatory.engine.BaseNettest @@ -14,7 +15,7 @@ import org.openobservatory.ooniprobe.R import org.openobservatory.ooniprobe.activity.AbstractActivity import org.openobservatory.ooniprobe.activity.MainActivity import org.openobservatory.ooniprobe.activity.OverviewActivity -import org.openobservatory.ooniprobe.activity.reviewdescriptorupdates.AvailableUpdatesViewModel +import org.openobservatory.ooniprobe.common.AppUpdatesViewModel import org.openobservatory.ooniprobe.activity.runtests.RunTestsActivity import org.openobservatory.ooniprobe.adapters.DashboardAdapter import org.openobservatory.ooniprobe.common.AbstractDescriptor @@ -42,7 +43,7 @@ class DashboardFragment : Fragment(), View.OnClickListener { lateinit var testStateRepository: TestStateRepository @Inject - lateinit var updatesViewModel: AvailableUpdatesViewModel + lateinit var updatesViewModel: AppUpdatesViewModel private lateinit var binding: FragmentDashboardBinding @@ -92,6 +93,13 @@ class DashboardFragment : Fragment(), View.OnClickListener { binding.swipeRefresh.isRefreshing = false } + binding.testsCompleted.setOnClickListener{ + (requireActivity() as MainActivity).showResults() + } + + updatesViewModel.testRunComplete.observe(viewLifecycleOwner) { testRunComplete -> + binding.testsCompleted.isVisible = testRunComplete ?: false + } updatesViewModel.descriptors.observe(viewLifecycleOwner) { descriptors -> descriptors.let { viewModel.updateDescriptorWith(it) } } @@ -110,6 +118,9 @@ class DashboardFragment : Fragment(), View.OnClickListener { ) binding.vpn.visibility = View.VISIBLE else binding.vpn.visibility = View.GONE updateDescriptors() + + binding.testsCompleted.isVisible = updatesViewModel.testRunComplete.value ?: false + } fun updateDescriptors() { diff --git a/app/src/main/java/org/openobservatory/ooniprobe/fragment/ProgressFragment.kt b/app/src/main/java/org/openobservatory/ooniprobe/fragment/ProgressFragment.kt index 7de0bbce5..aff9e330a 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/fragment/ProgressFragment.kt +++ b/app/src/main/java/org/openobservatory/ooniprobe/fragment/ProgressFragment.kt @@ -13,8 +13,11 @@ import androidx.core.content.ContextCompat import androidx.core.text.bold import androidx.fragment.app.Fragment import androidx.localbroadcastmanager.content.LocalBroadcastManager +import com.google.android.material.snackbar.Snackbar import org.openobservatory.ooniprobe.R +import org.openobservatory.ooniprobe.activity.MainActivity import org.openobservatory.ooniprobe.activity.RunningActivity +import org.openobservatory.ooniprobe.common.AppUpdatesViewModel import org.openobservatory.ooniprobe.common.Application import org.openobservatory.ooniprobe.common.OONITests import org.openobservatory.ooniprobe.common.PreferenceManager @@ -37,6 +40,9 @@ class ProgressFragment : Fragment() { @Inject lateinit var testProgressRepository: TestProgressRepository + @Inject + lateinit var appUpdatesViewModel: AppUpdatesViewModel + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { @@ -47,8 +53,14 @@ class ProgressFragment : Fragment() { ActivityCompat.startActivity(requireContext(), intent, null) } testProgressRepository.progress.observe(viewLifecycleOwner) { progressValue: Int? -> - if (progressValue != null) { - biding.progress.progress = progressValue + progressValue?.let { progress -> + biding.progress.progress = progress + } + } + testProgressRepository.eta.observe(viewLifecycleOwner) { etaValue: Double? -> + etaValue?.let { eta -> + biding.etaLayout.visibility = View.VISIBLE + biding.eta.text = RunningActivity.readableTimeRemaining(eta) } } return biding.root @@ -133,7 +145,8 @@ class ProgressFragment : Fragment() { biding.testImage.visibility = View.GONE } } - biding.name.text = when (task.currentSuite.name.equals(OONITests.EXPERIMENTAL.label)) { + biding.name.text = + when (task.currentSuite.name.equals(OONITests.EXPERIMENTAL.label)) { true -> SpannableStringBuilder().bold { append(currentTest.name) } false -> SpannableStringBuilder().bold { append(getString(currentTest.labelResId)) } }.append(" ") @@ -201,6 +214,7 @@ class ProgressFragment : Fragment() { override fun onEnd(context: Context) { biding.progressLayout.visibility = View.GONE + appUpdatesViewModel.testRunComplete.postValue(true) } } } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/fragment/ResultListFragment.java b/app/src/main/java/org/openobservatory/ooniprobe/fragment/ResultListFragment.java index 10cc8a8e8..ac54a3201 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/fragment/ResultListFragment.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/fragment/ResultListFragment.java @@ -24,6 +24,7 @@ import com.raizlabs.android.dbflow.sql.language.Method; import com.raizlabs.android.dbflow.sql.language.SQLite; +import org.openobservatory.engine.OONIRunNettest; import org.openobservatory.ooniprobe.R; import org.openobservatory.ooniprobe.activity.AbstractActivity; import org.openobservatory.ooniprobe.activity.ResultDetailActivity; @@ -50,11 +51,14 @@ import org.openobservatory.ooniprobe.model.database.Network; import org.openobservatory.ooniprobe.model.database.Result; import org.openobservatory.ooniprobe.model.database.Result_Table; +import org.openobservatory.ooniprobe.model.database.TestDescriptorKt; +import org.openobservatory.ooniprobe.test.test.WebConnectivity; import java.io.Serializable; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import javax.inject.Inject; @@ -204,7 +208,12 @@ void queryList(ResultListSpinnerItem filterItem) { } else if (result.test_group_name.equals(OONITests.EXPERIMENTAL.toString())) { items.add(new ExperimentalItem(result, this, this)); } else if (result.descriptor!=null) { - items.add(new RunItem(result, this, this)); + List nettests = TestDescriptorKt.getNettests(result.descriptor); + if (nettests.size()==1 && Objects.equals(nettests.get(0).getName(), WebConnectivity.NAME)){ + items.add(new WebsiteItem(result, this, this)); + } else { + items.add(new RunItem(result, this, this)); + } } else { items.add(new FailedItem(result, this, this)); } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/item/WebsiteItem.java b/app/src/main/java/org/openobservatory/ooniprobe/item/WebsiteItem.java index 60d9f0589..c992edde0 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/item/WebsiteItem.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/item/WebsiteItem.java @@ -1,5 +1,6 @@ package org.openobservatory.ooniprobe.item; +import android.content.Context; import android.text.format.DateFormat; import android.view.LayoutInflater; import android.view.View; @@ -7,8 +8,14 @@ import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import androidx.recyclerview.widget.RecyclerView; + +import com.google.common.base.Optional; + import localhost.toolkit.widget.recyclerview.HeterogeneousRecyclerItem; + +import org.openobservatory.engine.BaseNettest; import org.openobservatory.ooniprobe.R; +import org.openobservatory.ooniprobe.common.AbstractDescriptor; import org.openobservatory.ooniprobe.databinding.ItemWebsitesBinding; import org.openobservatory.ooniprobe.model.database.Measurement; import org.openobservatory.ooniprobe.model.database.Network; @@ -49,6 +56,14 @@ public void onBindViewHolder(ViewHolder viewHolder) { for (Measurement m : extra.getMeasurements()) allUploaded = allUploaded && (m.isUploaded() || m.is_failed); viewHolder.binding.startTime.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, allUploaded ? 0 : R.drawable.cloudoff, 0); + Context context = viewHolder.itemView.getContext(); + Optional> optionalDescriptor = extra.getDescriptor(context); + if (optionalDescriptor.isPresent()){ + viewHolder.binding.icon.setImageResource(optionalDescriptor.get().getDisplayIcon(context)); + viewHolder.binding.icon.setColorFilter(optionalDescriptor.get().getColor()); + viewHolder.binding.name.setText(optionalDescriptor.get().getTitle()); + viewHolder.binding.name.setTextColor(optionalDescriptor.get().getColor()); + } } public static class ViewHolder extends RecyclerView.ViewHolder { diff --git a/app/src/main/res/drawable-xxxhdpi/ic_menu_share.png b/app/src/main/res/drawable-xxxhdpi/ic_menu_share.png deleted file mode 100644 index ad160e433..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_menu_share.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_menu_share.xml b/app/src/main/res/drawable/ic_menu_share.xml new file mode 100644 index 000000000..74753b7ae --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_share.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/activity_overview.xml b/app/src/main/res/layout/activity_overview.xml index fb50caea0..e5b321a69 100644 --- a/app/src/main/res/layout/activity_overview.xml +++ b/app/src/main/res/layout/activity_overview.xml @@ -243,7 +243,7 @@ android:childDivider="@android:color/transparent" android:divider="@android:color/transparent" android:groupIndicator="@null" - android:paddingHorizontal="16dp" + android:paddingStart="16dp" app:layout_constraintTop_toBottomOf="@id/header" tools:listitem="@layout/overview_test_group_list_item" /> diff --git a/app/src/main/res/layout/activity_run_tests.xml b/app/src/main/res/layout/activity_run_tests.xml index f67c1ec09..cae28d12c 100644 --- a/app/src/main/res/layout/activity_run_tests.xml +++ b/app/src/main/res/layout/activity_run_tests.xml @@ -79,6 +79,8 @@ android:divider="@android:color/transparent" android:groupIndicator="@null" android:nestedScrollingEnabled="true" + android:listSelector="@android:color/transparent" + android:cacheColorHint="@android:color/transparent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="parent" diff --git a/app/src/main/res/layout/activity_running.xml b/app/src/main/res/layout/activity_running.xml index 0e721bf1b..d362cc6b3 100644 --- a/app/src/main/res/layout/activity_running.xml +++ b/app/src/main/res/layout/activity_running.xml @@ -21,8 +21,8 @@ android:layout_margin="8dp" android:minWidth="0dp" android:src="@drawable/chevron_right" - android:rotation="90" - android:tint="@android:color/white" /> + android:rotation="180" + app:tint="@android:color/white" /> - - + + + - @@ -36,6 +35,7 @@ android:gravity="center_vertical" android:text="@string/Dashboard_Running_PreparingTest" android:textColor="@color/color_gray9" /> + + + + + + + + + diff --git a/app/src/main/res/layout/item_websites.xml b/app/src/main/res/layout/item_websites.xml index df2e31473..e7a2d4b64 100644 --- a/app/src/main/res/layout/item_websites.xml +++ b/app/src/main/res/layout/item_websites.xml @@ -26,6 +26,7 @@ android:src="@drawable/test_websites" /> + android:padding="@dimen/item_padding_small"/> \ No newline at end of file diff --git a/app/src/main/res/layout/overview_test_group_list_item.xml b/app/src/main/res/layout/overview_test_group_list_item.xml index 93e960a07..2594fbb1a 100644 --- a/app/src/main/res/layout/overview_test_group_list_item.xml +++ b/app/src/main/res/layout/overview_test_group_list_item.xml @@ -5,7 +5,8 @@ tools:context=".activity.overview.OverviewTestsExpandableListViewAdapter" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal"> + android:orientation="horizontal" + android:paddingEnd="16dp" > + android:padding="@dimen/item_padding_small" /> \ No newline at end of file diff --git a/app/src/main/res/layout/run_tests_child_list_item.xml b/app/src/main/res/layout/run_tests_child_list_item.xml index 04c77b105..d092f7b9d 100644 --- a/app/src/main/res/layout/run_tests_child_list_item.xml +++ b/app/src/main/res/layout/run_tests_child_list_item.xml @@ -24,6 +24,7 @@ android:layout_centerVertical="true" android:layout_marginVertical="8dp" android:layout_marginEnd="16dp" + android:padding="@dimen/item_padding_small" android:src="@drawable/check_box_outline_blank" app:tint="@color/color_base" android:contentDescription="checkbox" /> diff --git a/app/src/main/res/layout/run_tests_group_divider.xml b/app/src/main/res/layout/run_tests_group_divider.xml new file mode 100644 index 000000000..ef14eaef6 --- /dev/null +++ b/app/src/main/res/layout/run_tests_group_divider.xml @@ -0,0 +1,31 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/run_tests_group_list_item.xml b/app/src/main/res/layout/run_tests_group_list_item.xml index ea9552ab4..a086b60b6 100644 --- a/app/src/main/res/layout/run_tests_group_list_item.xml +++ b/app/src/main/res/layout/run_tests_group_list_item.xml @@ -45,6 +45,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" + android:padding="@dimen/item_padding_small" android:layout_alignParentEnd="true" android:layout_centerVertical="true" android:src="@drawable/check_box_outline_blank" diff --git a/app/src/main/res/menu/share.xml b/app/src/main/res/menu/share.xml index e8b78dd6b..806aeacdd 100644 --- a/app/src/main/res/menu/share.xml +++ b/app/src/main/res/menu/share.xml @@ -4,8 +4,8 @@ + 4dp 8dp 16dp \ No newline at end of file