diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/ResultDetailActivity.java b/app/src/main/java/org/openobservatory/ooniprobe/activity/ResultDetailActivity.java deleted file mode 100644 index e80a6fafb..000000000 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/ResultDetailActivity.java +++ /dev/null @@ -1,243 +0,0 @@ -package org.openobservatory.ooniprobe.activity; - -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; -import androidx.core.app.ActivityCompat; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.viewpager2.adapter.FragmentStateAdapter; -import com.google.android.material.snackbar.Snackbar; -import com.google.android.material.tabs.TabLayoutMediator; -import localhost.toolkit.app.fragment.ConfirmDialogFragment; -import localhost.toolkit.widget.recyclerview.HeterogeneousRecyclerAdapter; -import localhost.toolkit.widget.recyclerview.HeterogeneousRecyclerItem; -import org.openobservatory.ooniprobe.R; -import org.openobservatory.ooniprobe.common.PreferenceManager; -import org.openobservatory.ooniprobe.common.ResubmitTask; -import org.openobservatory.ooniprobe.databinding.ActivityResultDetailBinding; -import org.openobservatory.ooniprobe.domain.GetResults; -import org.openobservatory.ooniprobe.domain.GetTestSuite; -import org.openobservatory.ooniprobe.fragment.resultHeader.ResultHeaderDetailFragment; -import org.openobservatory.ooniprobe.fragment.resultHeader.ResultHeaderMiddleboxFragment; -import org.openobservatory.ooniprobe.fragment.resultHeader.ResultHeaderPerformanceFragment; -import org.openobservatory.ooniprobe.fragment.resultHeader.ResultHeaderTBAFragment; -import org.openobservatory.ooniprobe.item.MeasurementItem; -import org.openobservatory.ooniprobe.item.MeasurementPerfItem; -import org.openobservatory.ooniprobe.model.database.Measurement; -import org.openobservatory.ooniprobe.model.database.Network; -import org.openobservatory.ooniprobe.model.database.Result; -import org.openobservatory.ooniprobe.test.suite.*; - -import javax.inject.Inject; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -public class ResultDetailActivity extends AbstractActivity implements View.OnClickListener, ConfirmDialogFragment.OnConfirmedListener { - private static final String ID = "id"; - private static final String UPLOAD_KEY = "upload"; - private static final String RERUN_KEY = "rerun"; - - private ArrayList items; - private HeterogeneousRecyclerAdapter adapter; - private Result result; - private Snackbar snackbar; - - @Inject - GetTestSuite getTestSuite; - - @Inject - GetResults getResults; - - @Inject - PreferenceManager preferenceManager; - - public static Intent newIntent(Context context, int id) { - return new Intent(context, ResultDetailActivity.class).putExtra(ID, id); - } - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getActivityComponent().inject(this); - result = getResults.get(getIntent().getIntExtra(ID, 0)); - assert result != null; - setTheme(result.getTestSuite().getThemeLight()); - ActivityResultDetailBinding binding = ActivityResultDetailBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - setSupportActionBar(binding.toolbar); - ActionBar bar = getSupportActionBar(); - if (bar != null) { - bar.setDisplayHomeAsUpEnabled(true); - bar.setTitle(result.getTestSuite().getTitle()); - } - binding.pager.setAdapter(new ResultHeaderAdapter(this)); - new TabLayoutMediator(binding.tabLayout, binding.pager, (tab, position) -> - tab.setText("●") - ).attach(); - LinearLayoutManager layoutManager = new LinearLayoutManager(this); - binding.recyclerView.setLayoutManager(layoutManager); - binding.recyclerView.addItemDecoration(new DividerItemDecoration(this, layoutManager.getOrientation())); - result.is_viewed = true; - result.save(); - items = new ArrayList<>(); - adapter = new HeterogeneousRecyclerAdapter<>(this, items); - binding.recyclerView.setAdapter(adapter); - snackbar = Snackbar.make(binding.coordinatorLayout, R.string.Snackbar_ResultsSomeNotUploaded_Text, Snackbar.LENGTH_INDEFINITE) - .setAction(R.string.Snackbar_ResultsSomeNotUploaded_UploadAll, v1 -> runAsyncTask()); - } - - @Override - protected void onResume() { - super.onResume(); - load(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.rerun, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - invalidateOptionsMenu(); - if (!result.test_group_name.equals(WebsitesSuite.NAME)) - menu.findItem(R.id.reRun).setVisible(false); - return super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.reRun: - new ConfirmDialogFragment.Builder() - .withExtra(RERUN_KEY) - .withMessage(getString(R.string.Modal_ReRun_Websites_Title, String.valueOf(result.getMeasurements().size()))) - .withPositiveButton(getString(R.string.Modal_ReRun_Websites_Run)) - .build().show(getSupportFragmentManager(), null); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - private void reTestWebsites() { - RunningActivity.runAsForegroundService(this, getTestSuite.getFrom(result).asArray(),this::finish, preferenceManager); - } - - private void runAsyncTask() { - new ResubmitAsyncTask(this).execute(result.id, null); - } - - private void load() { - result = getResults.get(result.id); - assert result != null; - boolean isPerf = result.test_group_name.equals(PerformanceSuite.NAME); - items.clear(); - List measurements = result.getMeasurementsSorted(); - for (Measurement measurement : measurements) - items.add(isPerf && !measurement.is_failed ? - new MeasurementPerfItem(measurement, this) : - new MeasurementItem(measurement, this)); - adapter.notifyTypesChanged(); - if (Measurement.hasReport(this, Measurement.selectUploadableWithResultId(result.id))) - snackbar.show(); - else - snackbar.dismiss(); - } - - @Override - public void onClick(View v) { - Measurement measurement = (Measurement) v.getTag(); - if (result.test_group_name.equals(ExperimentalSuite.NAME)) - startActivity(TextActivity.newIntent(this, TextActivity.TYPE_JSON, measurement)); - else - ActivityCompat.startActivity(this, MeasurementDetailActivity.newIntent(this, measurement.id), null); - } - - @Override - public void onConfirmation(Serializable extra, int buttonClicked) { - if (buttonClicked == DialogInterface.BUTTON_POSITIVE && extra.equals(UPLOAD_KEY)) - runAsyncTask(); - else if (buttonClicked == DialogInterface.BUTTON_POSITIVE && extra.equals(RERUN_KEY)) - reTestWebsites(); - else if (buttonClicked == DialogInterface.BUTTON_NEUTRAL) - startActivity(TextActivity.newIntent(this, TextActivity.TYPE_UPLOAD_LOG, (String)extra)); - } - - private static class ResubmitAsyncTask extends ResubmitTask { - ResubmitAsyncTask(ResultDetailActivity activity) { - super(activity, activity.preferenceManager.getProxyURL()); - } - - @Override - protected void onPostExecute(Boolean result) { - super.onPostExecute(result); - if (getActivity() != null) { - getActivity().result = d.getResults.get(getActivity().result.id); - getActivity().load(); - if (!result) - new ConfirmDialogFragment.Builder() - .withExtra(UPLOAD_KEY) - .withTitle(getActivity().getString(R.string.Modal_UploadFailed_Title)) - .withMessage(getActivity().getString(R.string.Modal_UploadFailed_Paragraph, errors.toString(), totUploads.toString())) - .withPositiveButton(getActivity().getString(R.string.Modal_Retry)) - .withNeutralButton(getActivity().getString(R.string.Modal_DisplayFailureLog)) - .withExtra(String.join("\n", logger.logs)) - .build().show(getActivity().getSupportFragmentManager(), null); - } - } - } - - private class ResultHeaderAdapter extends FragmentStateAdapter { - ResultHeaderAdapter(final FragmentActivity fa) { - super(fa); - } - - @Override - public Fragment createFragment(int position) { - if (result.test_group_name.equals(ExperimentalSuite.NAME)){ - if (position == 0) - return ResultHeaderDetailFragment.newInstance(false, result.getFormattedDataUsageUp(), result.getFormattedDataUsageDown(), result.start_time, result.getRuntime(), true, null, null); - else if (position == 1) - return ResultHeaderDetailFragment.newInstance(false, null, null, null, null, null, Network.getCountry(ResultDetailActivity.this, result.network), result.network); - } - if (position == 1) - return ResultHeaderDetailFragment.newInstance(false, result.getFormattedDataUsageUp(), result.getFormattedDataUsageDown(), result.start_time, result.getRuntime(), true, null, null); - else if (position == 2) - return ResultHeaderDetailFragment.newInstance(false, null, null, null, null, null, Network.getCountry(ResultDetailActivity.this, result.network), result.network); - else switch (result.test_group_name) { - default: //Default can no longer be null, so we have to default to something... - // NOTE: Perhaps set up a test page? - case WebsitesSuite.NAME: - return ResultHeaderTBAFragment.newInstance(result); - case InstantMessagingSuite.NAME: - return ResultHeaderTBAFragment.newInstance(result); - case MiddleBoxesSuite.NAME: - return ResultHeaderMiddleboxFragment.newInstance(result.countAnomalousMeasurements() > 0); - case PerformanceSuite.NAME: - return ResultHeaderPerformanceFragment.newInstance(result); - case CircumventionSuite.NAME: - return ResultHeaderTBAFragment.newInstance(result); - } - } - - - @Override - public int getItemCount() { - if (result.test_group_name.equals(ExperimentalSuite.NAME)) - return 2; - return 3; - } - } -} diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/ResultDetailActivity.kt b/app/src/main/java/org/openobservatory/ooniprobe/activity/ResultDetailActivity.kt new file mode 100644 index 000000000..1b4231c8a --- /dev/null +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/ResultDetailActivity.kt @@ -0,0 +1,298 @@ +package org.openobservatory.ooniprobe.activity + +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.core.app.ActivityCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.google.android.material.snackbar.Snackbar +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator +import localhost.toolkit.app.fragment.ConfirmDialogFragment +import localhost.toolkit.app.fragment.ConfirmDialogFragment.OnConfirmedListener +import org.openobservatory.ooniprobe.R +import org.openobservatory.ooniprobe.adapters.MeasurementGroup +import org.openobservatory.ooniprobe.adapters.ResultDetailExpandableListAdapter +import org.openobservatory.ooniprobe.common.PreferenceManager +import org.openobservatory.ooniprobe.common.ResubmitTask +import org.openobservatory.ooniprobe.databinding.ActivityResultDetailBinding +import org.openobservatory.ooniprobe.domain.GetResults +import org.openobservatory.ooniprobe.domain.GetTestSuite +import org.openobservatory.ooniprobe.fragment.resultHeader.ResultHeaderDetailFragment +import org.openobservatory.ooniprobe.fragment.resultHeader.ResultHeaderMiddleboxFragment +import org.openobservatory.ooniprobe.fragment.resultHeader.ResultHeaderPerformanceFragment +import org.openobservatory.ooniprobe.fragment.resultHeader.ResultHeaderTBAFragment +import org.openobservatory.ooniprobe.model.database.Measurement +import org.openobservatory.ooniprobe.model.database.Network +import org.openobservatory.ooniprobe.model.database.Result +import org.openobservatory.ooniprobe.test.suite.* +import java.io.Serializable +import javax.inject.Inject + +class ResultDetailActivity : AbstractActivity(), View.OnClickListener, OnConfirmedListener { + + companion object { + private const val ID = "id" + private const val UPLOAD_KEY = "upload" + private const val RERUN_KEY = "rerun" + + @JvmStatic + fun newIntent(context: Context?, id: Int): Intent { + return Intent(context, ResultDetailActivity::class.java).putExtra(ID, id) + } + } + + private lateinit var result: Result + private lateinit var snackbar: Snackbar + private lateinit var binding: ActivityResultDetailBinding + + @Inject + lateinit var getTestSuite: GetTestSuite + + @Inject + lateinit var getResults: GetResults + + @Inject + lateinit var preferenceManager: PreferenceManager + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + activityComponent.inject(this) + when (val iResult = getResults[intent.getIntExtra(ID, 0)]) { + null -> { + /** + * Close the activity if the result is not found. This should never happen. + * Previous use of 'assert' closed the entire app. + */ + finish() + return + } + + else -> { + result = iResult + setTheme(result.testSuite.themeLight) + binding = ActivityResultDetailBinding.inflate(layoutInflater) + setContentView(binding.root) + setSupportActionBar(binding.toolbar) + supportActionBar?.let { actionBar -> + actionBar.setDisplayHomeAsUpEnabled(true) + actionBar.setTitle(result.testSuite.title) + } + binding.pager.apply { + setAdapter(ResultHeaderAdapter(this@ResultDetailActivity)) + TabLayoutMediator(binding.tabLayout, this) + { tab: TabLayout.Tab, _: Int -> tab.setText("●") }.attach() + } + result.apply { + is_viewed = true + save() + } + + snackbar = Snackbar.make( + binding.coordinatorLayout, + R.string.Snackbar_ResultsSomeNotUploaded_Text, + Snackbar.LENGTH_INDEFINITE + ).setAction(R.string.Snackbar_ResultsSomeNotUploaded_UploadAll) { runAsyncTask() } + load() + } + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.rerun, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onPrepareOptionsMenu(menu: Menu): Boolean { + invalidateOptionsMenu() + if (result.test_group_name != WebsitesSuite.NAME) menu.findItem(R.id.reRun).setVisible(false) + return super.onPrepareOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.reRun -> { + ConfirmDialogFragment.Builder() + .withExtra(RERUN_KEY) + .withMessage( + getString( + R.string.Modal_ReRun_Websites_Title, + result.getMeasurements().size.toString() + ) + ) + .withPositiveButton(getString(R.string.Modal_ReRun_Websites_Run)) + .build().show(supportFragmentManager, null) + true + } + + else -> super.onOptionsItemSelected(item) + } + } + + private fun reTestWebsites() { + RunningActivity.runAsForegroundService( + this, + getTestSuite.getFrom(result).asArray(), + { finish() }, + preferenceManager + ) + } + + private fun runAsyncTask() { + ResubmitAsyncTask(this).execute(result.id, null) + } + + private fun load() { + result = getResults[result.id] + + val groupedItemList = mutableListOf() + val groupedItems = result.getMeasurementsSorted().groupBy { it.test_name } + for ((_, itemList) in groupedItems) { + if (itemList.size == 1) { + groupedItemList.add(itemList.first()) + } else { + if (groupedItems.size == 1) { + groupedItemList.addAll(itemList) + } else { + groupedItemList.add(MeasurementGroup(title = itemList.first().test.name, measurements = itemList)) + } + + } + } + + binding.recyclerView.apply { + setAdapter( + ResultDetailExpandableListAdapter(groupedItemList, this@ResultDetailActivity) + ) + } + + if (Measurement.hasReport( + this, + Measurement.selectUploadableWithResultId(result.id) + ) + ) snackbar.show() else snackbar.dismiss() + } + + override fun onClick(v: View) { + val measurement = v.tag as Measurement + if (result.test_group_name == ExperimentalSuite.NAME) startActivity( + TextActivity.newIntent( + this, + TextActivity.TYPE_JSON, + measurement + ) + ) else ActivityCompat.startActivity(this, MeasurementDetailActivity.newIntent(this, measurement.id), null) + } + + override fun onConfirmation(extra: Serializable, buttonClicked: Int) { + if (buttonClicked == DialogInterface.BUTTON_POSITIVE && extra == UPLOAD_KEY) { + runAsyncTask() + } else if (buttonClicked == DialogInterface.BUTTON_POSITIVE && extra == RERUN_KEY) { + reTestWebsites() + } else if (buttonClicked == DialogInterface.BUTTON_NEUTRAL) { + startActivity( + TextActivity.newIntent(this, TextActivity.TYPE_UPLOAD_LOG, extra as String) + ) + } + } + + private class ResubmitAsyncTask(activity: ResultDetailActivity) : + ResubmitTask(activity, activity.preferenceManager.proxyURL) { + override fun onPostExecute(result: Boolean) { + super.onPostExecute(result) + if (getActivity() != null) { + getActivity()!!.result = d.getResults[getActivity()!!.result.id] + getActivity()!!.load() + if (!result) ConfirmDialogFragment.Builder() + .withExtra(UPLOAD_KEY) + .withTitle(getActivity()!!.getString(R.string.Modal_UploadFailed_Title)) + .withMessage( + getActivity()!!.getString( + R.string.Modal_UploadFailed_Paragraph, + errors.toString(), + totUploads.toString() + ) + ) + .withPositiveButton(getActivity()!!.getString(R.string.Modal_Retry)) + .withNeutralButton(getActivity()!!.getString(R.string.Modal_DisplayFailureLog)) + .withExtra(java.lang.String.join("\n", logger.logs)) + .build().show(getActivity()!!.supportFragmentManager, null) + } + } + } + + private inner class ResultHeaderAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { + override fun createFragment(position: Int): Fragment { + when (result.test_group_name) { + ExperimentalSuite.NAME -> { + when (position) { + 0 -> return ResultHeaderDetailFragment.newInstance( + false, + result.formattedDataUsageUp, + result.formattedDataUsageDown, + result.start_time, + result.getRuntime(), + true, + null, + null + ) + + 1 -> return ResultHeaderDetailFragment.newInstance( + false, + null, + null, + null, + null, + null, + Network.getCountry(this@ResultDetailActivity, result.network), + result.network + ) + } + } + } + return when (position) { + 1 -> ResultHeaderDetailFragment.newInstance( + false, + result.formattedDataUsageUp, + result.formattedDataUsageDown, + result.start_time, + result.getRuntime(), + true, + null, + null + ) + + 2 -> ResultHeaderDetailFragment.newInstance( + false, + null, + null, + null, + null, + null, + Network.getCountry(this@ResultDetailActivity, result.network), + result.network + ) + + else -> when (result.test_group_name) { + WebsitesSuite.NAME -> ResultHeaderTBAFragment.newInstance(result) + InstantMessagingSuite.NAME -> ResultHeaderTBAFragment.newInstance(result) + MiddleBoxesSuite.NAME -> ResultHeaderMiddleboxFragment.newInstance(result.countAnomalousMeasurements() > 0) + PerformanceSuite.NAME -> ResultHeaderPerformanceFragment.newInstance(result) + CircumventionSuite.NAME -> ResultHeaderTBAFragment.newInstance(result) + else -> ResultHeaderTBAFragment.newInstance(result) + } + } + } + + override fun getItemCount(): Int { + return if (result.test_group_name == ExperimentalSuite.NAME) 2 else 3 + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/openobservatory/ooniprobe/adapters/ResultDetailExpandableListAdapter.kt b/app/src/main/java/org/openobservatory/ooniprobe/adapters/ResultDetailExpandableListAdapter.kt new file mode 100755 index 000000000..46fd48604 --- /dev/null +++ b/app/src/main/java/org/openobservatory/ooniprobe/adapters/ResultDetailExpandableListAdapter.kt @@ -0,0 +1,207 @@ +package org.openobservatory.ooniprobe.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseExpandableListAdapter +import android.widget.TextView +import androidx.core.content.ContextCompat +import org.openobservatory.ooniprobe.R +import org.openobservatory.ooniprobe.model.database.Measurement +import org.openobservatory.ooniprobe.test.test.* + +data class MeasurementGroup(val title: String, val measurements: List) + + +class ResultDetailExpandableListAdapter( + private val items: List, + private val onClickListener: View.OnClickListener +) : BaseExpandableListAdapter() { + + override fun getGroupCount() = items.size + + override fun getChildrenCount(listPosition: Int): Int = items[listPosition].let { + when (it) { + is MeasurementGroup -> it.measurements.size + + else -> 0 + } + } + + override fun getGroup(listPosition: Int): Any { + return when { + items[listPosition] is MeasurementGroup -> (items[listPosition] as MeasurementGroup).title + else -> items[listPosition] + } + } + + override fun getChild(listPosition: Int, expandedListPosition: Int): Measurement? = when { + items[listPosition] is MeasurementGroup -> (items[listPosition] as MeasurementGroup).measurements[expandedListPosition] + else -> null + } + + override fun getGroupId(listPosition: Int): Long = listPosition.toLong() + + override fun getChildId(listPosition: Int, expandedListPosition: Int): Long = expandedListPosition.toLong() + + override fun hasStableIds(): Boolean = false + + override fun isChildSelectable(listPosition: Int, expandedListPosition: Int): Boolean = true + + override fun getChildView( + groupPosition: Int, + childPosition: Int, + isLastChild: Boolean, + convertView: View?, + parent: ViewGroup + ): View { + val measurement = getChild(groupPosition, childPosition) + + val root = convertView ?: LayoutInflater.from(parent.context) + .inflate(R.layout.item_measurement, parent, false) + + measurement?.let { + bindMeasurement(it, root) + root.apply { + setPaddingRelative(96, 0, 0, 0) + setBackgroundColor(parent.context.resources.getColor(R.color.color_gray0)) + } + } ?: run { + root.visibility = View.GONE + } + + return root + } + + + override fun getGroupView( + groupPosition: Int, + isExpanded: Boolean, + convertView: View?, + parent: ViewGroup + ): View { + val groupItem = getGroup(groupPosition) + + val root = convertView ?: LayoutInflater.from(parent.context) + .inflate(R.layout.item_measurement, parent, false) + + when (groupItem) { + is Measurement -> bindMeasurement(groupItem, root) + + else -> { + root.findViewById(R.id.text).text = groupItem.toString() + root.findViewById(R.id.indicator).apply { + visibility = View.VISIBLE + text = "${(items[groupPosition] as MeasurementGroup).measurements.size} Inputs" + setCompoundDrawablesRelativeWithIntrinsicBounds( + null, + null, + ContextCompat.getDrawable(root.context,if (isExpanded) R.drawable.keyboard_arrow_up else R.drawable.keyboard_arrow_down)?.apply { + setTint(ContextCompat.getColor(root.context, R.color.color_black)) + }, + null + ) + } + + } + } + return root + } + + private fun bindMeasurement( + measurement: Measurement, + view: View + ) { + view.tag = measurement + view.setOnClickListener(onClickListener) + view.findViewById(R.id.text).also { textView -> + + val test: AbstractTest = measurement.getTest() + + val endDrawable: Int = when { + measurement.is_failed -> R.drawable.error_24dp + measurement.is_anomaly && measurement.isUploaded -> R.drawable.exclamation_24dp + measurement.is_anomaly -> R.drawable.exclamation_cloudoff + measurement.isUploaded -> R.drawable.tick_green_24dp + else -> R.drawable.tick_green_cloudoff + } + + if (measurement.test_name == WebConnectivity.NAME) { + if (measurement.url != null) { + textView.text = measurement.url.url + textView.setCompoundDrawablesRelativeWithIntrinsicBounds( + measurement.url.getCategoryIcon(textView.context), + 0, + endDrawable, + 0 + ) + } + } else { + when (measurement.getTest().labelResId) { + R.string.Test_Experimental_Fullname -> textView.text = measurement.getTest().name + else -> textView.setText(test.labelResId) + } + textView.setCompoundDrawablesRelativeWithIntrinsicBounds(test.iconResId, 0, endDrawable, 0) + } + } + if (arrayListOf( + Dash.NAME, + Ndt.NAME, + HttpHeaderFieldManipulation.NAME, + HttpInvalidRequestLine.NAME + ).contains(measurement.test_name) + ) { + val c: Context = view.context + view.findViewById(R.id.pref_group).visibility = View.VISIBLE + val data1: TextView = view.findViewById(R.id.data1) + val data2: TextView = view.findViewById(R.id.data2) + view.findViewById(R.id.text).setCompoundDrawablesRelativeWithIntrinsicBounds( + 0, + 0, + if (measurement.is_failed || measurement.isUploaded) 0 else R.drawable.cloudoff, + 0 + ) + when (measurement.test_name) { + Dash.NAME -> { + data1.apply { + setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.video_quality, 0, 0, 0) + setText(measurement.getTestKeys().getVideoQuality(true)) + } + data2.visibility = View.GONE + } + + Ndt.NAME -> { + data1.apply { + setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.download_black, 0, 0, 0) + text = c.getString( + R.string.twoParam, + measurement.getTestKeys().getDownload(c), + c.getString(measurement.getTestKeys().getDownloadUnit()) + ) + } + data2.apply { + visibility = View.VISIBLE + setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.upload_black, 0, 0, 0) + text = c.getString( + R.string.twoParam, + measurement.getTestKeys().getUpload(c), + c.getString(measurement.getTestKeys().getUploadUnit()) + ) + } + } + + HttpHeaderFieldManipulation.NAME, HttpInvalidRequestLine.NAME -> { + data1.apply { + setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.test_middle_boxes_small, 0, 0, 0) + (if (measurement.is_anomaly) c.getString(R.string.TestResults_Overview_MiddleBoxes_Found) else c.getString( + R.string.TestResults_Overview_MiddleBoxes_NotFound + )).also { text = it } + } + data2.visibility = View.GONE + } + } + } + } + +} diff --git a/app/src/main/java/org/openobservatory/ooniprobe/item/MeasurementItem.java b/app/src/main/java/org/openobservatory/ooniprobe/item/MeasurementItem.java index fedfa4c6a..657d46c9e 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/item/MeasurementItem.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/item/MeasurementItem.java @@ -18,6 +18,7 @@ import localhost.toolkit.widget.recyclerview.HeterogeneousRecyclerItem; +@Deprecated public class MeasurementItem extends HeterogeneousRecyclerItem { private final View.OnClickListener onClickListener; diff --git a/app/src/main/java/org/openobservatory/ooniprobe/item/MeasurementPerfItem.java b/app/src/main/java/org/openobservatory/ooniprobe/item/MeasurementPerfItem.java index 674f0a837..096672eed 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/item/MeasurementPerfItem.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/item/MeasurementPerfItem.java @@ -16,7 +16,7 @@ import org.openobservatory.ooniprobe.test.test.Ndt; import localhost.toolkit.widget.recyclerview.HeterogeneousRecyclerItem; - +@Deprecated public class MeasurementPerfItem extends HeterogeneousRecyclerItem { private final View.OnClickListener onClickListener; diff --git a/app/src/main/res/drawable/keyboard_arrow_down.xml b/app/src/main/res/drawable/keyboard_arrow_down.xml new file mode 100644 index 000000000..9e345b86c --- /dev/null +++ b/app/src/main/res/drawable/keyboard_arrow_down.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/keyboard_arrow_up.xml b/app/src/main/res/drawable/keyboard_arrow_up.xml new file mode 100644 index 000000000..1227182e4 --- /dev/null +++ b/app/src/main/res/drawable/keyboard_arrow_up.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_result_detail.xml b/app/src/main/res/layout/activity_result_detail.xml index c09cf62ad..ea34bc7aa 100644 --- a/app/src/main/res/layout/activity_result_detail.xml +++ b/app/src/main/res/layout/activity_result_detail.xml @@ -57,12 +57,11 @@ - - \ No newline at end of file + + + + + + + + + + + +