From f1392aaaa4f52f3fb1fad2e920cf78106efbf81b Mon Sep 17 00:00:00 2001 From: Norbel Ambanumben Date: Wed, 30 Aug 2023 07:23:01 +0100 Subject: [PATCH 01/50] Replace `HeterogeneousRecyclerAdapter` with `RecyclerView.Adapter` and add a `ViewModel` for state management --- app/build.gradle | 17 ++- .../ooniprobe/adapters/DashboardAdapter.kt | 105 +++++++++++++ .../ooniprobe/fragment/DashboardFragment.java | 140 ------------------ .../ooniprobe/fragment/DashboardFragment.kt | 120 +++++++++++++++ .../fragment/dashboard/DashboardViewModel.kt | 44 ++++++ .../ooniprobe/item/SeperatorItem.java | 1 + .../ooniprobe/item/TestsuiteItem.java | 1 + 7 files changed, 282 insertions(+), 146 deletions(-) create mode 100644 app/src/main/java/org/openobservatory/ooniprobe/adapters/DashboardAdapter.kt delete mode 100644 app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.java create mode 100644 app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.kt create mode 100644 app/src/main/java/org/openobservatory/ooniprobe/fragment/dashboard/DashboardViewModel.kt diff --git a/app/build.gradle b/app/build.gradle index ac15c07d7..763959db0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,6 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' apply from: 'jacoco.gradle' android { @@ -94,6 +96,9 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } buildFeatures { viewBinding = true } @@ -114,7 +119,7 @@ dependencies { implementation 'androidx.legacy:legacy-support-v4:1.0.0' // Third-party - annotationProcessor 'com.github.Raizlabs.DBFlow:dbflow-processor:4.2.4' + kapt 'com.github.Raizlabs.DBFlow:dbflow-processor:4.2.4' implementation 'com.github.Raizlabs.DBFlow:dbflow-core:4.2.4' implementation 'com.github.Raizlabs.DBFlow:dbflow:4.2.4' @@ -124,7 +129,7 @@ dependencies { implementation 'com.jakewharton:butterknife:10.2.3' - annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3' + kapt 'com.jakewharton:butterknife-compiler:10.2.3' implementation 'com.github.xanscale.LocalhostToolkit:app:19.05.01' implementation 'com.airbnb.android:lottie:3.0.7' @@ -142,8 +147,8 @@ dependencies { fullImplementation 'com.google.android.play:core:1.10.3' // Dependency Injection - implementation 'com.google.dagger:dagger:2.36' - annotationProcessor 'com.google.dagger:dagger-compiler:2.36' + implementation 'com.google.dagger:dagger:2.45' + kapt 'com.google.dagger:dagger-compiler:2.45' // Logger implementation project(':applogger') @@ -159,7 +164,7 @@ dependencies { testImplementation 'org.robolectric:robolectric:4.5.1' testImplementation 'com.github.blocoio:faker:1.2.8' testImplementation 'org.ooni:oonimkall:2023.07.18-162729' - testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.36' + testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.45' // Instrumentation Testing androidTestImplementation 'tools.fastlane:screengrab:2.0.0' @@ -179,7 +184,7 @@ dependencies { exclude module: 'recyclerview-v7' } androidTestImplementation('com.schibsted.spain:barista:3.9.0') - androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.36" + androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.45" } static def versionCodeDate() { diff --git a/app/src/main/java/org/openobservatory/ooniprobe/adapters/DashboardAdapter.kt b/app/src/main/java/org/openobservatory/ooniprobe/adapters/DashboardAdapter.kt new file mode 100644 index 000000000..26baea9f4 --- /dev/null +++ b/app/src/main/java/org/openobservatory/ooniprobe/adapters/DashboardAdapter.kt @@ -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, + private val onClickListener: View.OnClickListener, + private val preferenceManager: PreferenceManager, +) : RecyclerView.Adapter() { + + 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) +} \ No newline at end of file diff --git a/app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.java b/app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.java deleted file mode 100644 index 7eee1ca50..000000000 --- a/app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.java +++ /dev/null @@ -1,140 +0,0 @@ -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.annotation.NonNull; -import androidx.annotation.Nullable; -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.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.item.SeperatorItem; -import org.openobservatory.ooniprobe.item.TestsuiteItem; -import org.openobservatory.ooniprobe.model.database.Result; -import org.openobservatory.ooniprobe.test.TestAsyncTask; -import org.openobservatory.ooniprobe.test.suite.AbstractSuite; - -import java.util.ArrayList; - -import javax.inject.Inject; - -import localhost.toolkit.widget.recyclerview.HeterogeneousRecyclerAdapter; -import localhost.toolkit.widget.recyclerview.HeterogeneousRecyclerItem; - -public class DashboardFragment extends Fragment implements View.OnClickListener { - - @Inject - PreferenceManager preferenceManager; - - private ArrayList items; - - private ArrayList testSuites; - - private HeterogeneousRecyclerAdapter adapter; - - private FragmentDashboardBinding binding; - - @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { - binding = FragmentDashboardBinding.inflate(inflater,container,false); - ((Application) getActivity().getApplication()).getFragmentComponent().inject(this); - ((AppCompatActivity) getActivity()).setSupportActionBar(binding.toolbar); - ((AppCompatActivity) getActivity()).getSupportActionBar().setTitle(null); - items = new ArrayList<>(); - testSuites = new ArrayList<>(); - adapter = new HeterogeneousRecyclerAdapter<>(getActivity(), items); - binding.recycler.setAdapter(adapter); - binding.recycler.setLayoutManager(new LinearLayoutManager(getActivity())); - binding.runAll.setOnClickListener(v1 -> runAll()); - binding.vpn.setOnClickListener(view -> ((Application) getActivity().getApplication()).openVPNSettings()); - return binding.getRoot(); - } - - @Override public void onResume() { - super.onResume(); - items.clear(); - testSuites.clear(); - testSuites.addAll(TestAsyncTask.getSuites()); - - ArrayList emptySuites = new ArrayList<>(); - for (AbstractSuite testSuite : testSuites){ - if(testSuite.getTestList(preferenceManager).length > 0){ - items.add(new TestsuiteItem(testSuite, this, preferenceManager)); - } else { - emptySuites.add(testSuite); - } - } - - if(!emptySuites.isEmpty()){ - items.add(new SeperatorItem()); - - for(AbstractSuite emptyTest: emptySuites) - items.add(new TestsuiteItem(emptyTest, this, preferenceManager)); - } - - - - setLastTest(); - adapter.notifyTypesChanged(); - if (ReachabilityManager.isVPNinUse(this.getContext()) - && preferenceManager.isWarnVPNInUse()) - binding.vpn.setVisibility(View.VISIBLE); - else - binding.vpn.setVisibility(View.GONE); - } - - private void setLastTest() { - Result lastResult = Result.getLastResult(); - if (lastResult == null) - binding.lastTested.setText(getString(R.string.Dashboard_Overview_LatestTest) - + " " + - getString(R.string.Dashboard_Overview_LastRun_Never)); - else - binding.lastTested.setText(getString(R.string.Dashboard_Overview_LatestTest) - + " " + - DateUtils.getRelativeTimeSpanString(lastResult.start_time.getTime())); - } - - public void runAll() { - RunningActivity.runAsForegroundService((AbstractActivity) getActivity(), testSuites, this::onTestServiceStartedListener, preferenceManager); - } - - private void onTestServiceStartedListener() { - try { - ((AbstractActivity) getActivity()).bindTestService(); - } catch (Exception e) { - e.printStackTrace(); - ThirdPartyServices.logException(e); - } - } - - @Override public void onClick(View v) { - AbstractSuite testSuite = (AbstractSuite) v.getTag(); - switch (v.getId()) { - case R.id.run: - RunningActivity.runAsForegroundService( - (AbstractActivity) getActivity(), - testSuite.asArray(), - this::onTestServiceStartedListener, - preferenceManager - ); - break; - default: - ActivityCompat.startActivity(getActivity(), OverviewActivity.newIntent(getActivity(), testSuite), null); - break; - } - } -} diff --git a/app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.kt b/app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.kt new file mode 100644 index 000000000..a973b1cee --- /dev/null +++ b/app/src/main/java/org/openobservatory/ooniprobe/fragment/DashboardFragment.kt @@ -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 = 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 + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/openobservatory/ooniprobe/fragment/dashboard/DashboardViewModel.kt b/app/src/main/java/org/openobservatory/ooniprobe/fragment/dashboard/DashboardViewModel.kt new file mode 100644 index 000000000..009a3cab0 --- /dev/null +++ b/app/src/main/java/org/openobservatory/ooniprobe/fragment/dashboard/DashboardViewModel.kt @@ -0,0 +1,44 @@ +package org.openobservatory.ooniprobe.fragment.dashboard + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.openobservatory.ooniprobe.common.PreferenceManager +import org.openobservatory.ooniprobe.test.TestAsyncTask +import org.openobservatory.ooniprobe.test.suite.AbstractSuite +import javax.inject.Inject + +class DashboardViewModel @Inject constructor(private val preferenceManager: PreferenceManager) : ViewModel() { + private val enabledTitle: String = "Enabled" + private val groupedItemList = MutableLiveData>() + val items = MutableLiveData>(TestAsyncTask.getSuites()) + + fun getGroupedItemList(): LiveData> { + if (groupedItemList.value == null) { + fetchItemList() + } + return groupedItemList + } + + private fun fetchItemList() { + + val groupedItems = items.value!!.sortedBy { it.getTestList(preferenceManager).isEmpty() } + .groupBy { + return@groupBy if ((it.getTestList(preferenceManager).isNotEmpty())) { + enabledTitle + } else { + "" + } + } + + val groupedItemList = mutableListOf() + groupedItems.forEach { (status, itemList) -> + if (status != enabledTitle){ + groupedItemList.add(status) + } + groupedItemList.addAll(itemList) + } + + this.groupedItemList.value = groupedItemList + } +} \ No newline at end of file diff --git a/app/src/main/java/org/openobservatory/ooniprobe/item/SeperatorItem.java b/app/src/main/java/org/openobservatory/ooniprobe/item/SeperatorItem.java index 5b50974d5..454be6618 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/item/SeperatorItem.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/item/SeperatorItem.java @@ -11,6 +11,7 @@ import butterknife.ButterKnife; import localhost.toolkit.widget.recyclerview.HeterogeneousRecyclerItem; +@Deprecated public class SeperatorItem extends HeterogeneousRecyclerItem { public SeperatorItem() { diff --git a/app/src/main/java/org/openobservatory/ooniprobe/item/TestsuiteItem.java b/app/src/main/java/org/openobservatory/ooniprobe/item/TestsuiteItem.java index 9eb8bf60b..a334a751b 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/item/TestsuiteItem.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/item/TestsuiteItem.java @@ -13,6 +13,7 @@ import org.openobservatory.ooniprobe.databinding.ItemTestsuiteBinding; import org.openobservatory.ooniprobe.test.suite.AbstractSuite; +@Deprecated public class TestsuiteItem extends HeterogeneousRecyclerItem { private final View.OnClickListener onClickListener; private final PreferenceManager preferenceManager; From 5ac447939a85ae3022bcb4da824e0c1f5cc4b47f Mon Sep 17 00:00:00 2001 From: Norbel Ambanumben Date: Wed, 30 Aug 2023 08:23:03 +0100 Subject: [PATCH 02/50] Update annotation processing for tests --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 763959db0..1204bc5d7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -164,7 +164,7 @@ dependencies { testImplementation 'org.robolectric:robolectric:4.5.1' testImplementation 'com.github.blocoio:faker:1.2.8' testImplementation 'org.ooni:oonimkall:2023.07.18-162729' - testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.45' + kaptTest 'com.google.dagger:dagger-compiler:2.45' // Instrumentation Testing androidTestImplementation 'tools.fastlane:screengrab:2.0.0' @@ -184,7 +184,7 @@ dependencies { exclude module: 'recyclerview-v7' } androidTestImplementation('com.schibsted.spain:barista:3.9.0') - androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:2.45" + kaptAndroidTest "com.google.dagger:dagger-compiler:2.45" } static def versionCodeDate() { From 631230a80f025e19b77398fe5cd05fd5932e9691 Mon Sep 17 00:00:00 2001 From: Norbel Ambanumben Date: Wed, 18 Oct 2023 12:22:24 +0100 Subject: [PATCH 03/50] Updated shared module --- shared-test/build.gradle | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shared-test/build.gradle b/shared-test/build.gradle index 607a28dff..1877880b9 100644 --- a/shared-test/build.gradle +++ b/shared-test/build.gradle @@ -1,5 +1,7 @@ plugins { id 'com.android.library' + id 'kotlin-android' + id 'kotlin-kapt' } android { @@ -9,10 +11,8 @@ android { defaultConfig { minSdk 21 } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 } flavorDimensions = ['testing', 'license'] productFlavors { @@ -41,12 +41,12 @@ dependencies { // Dependency Injection (https://dagger.dev/) implementation libs.google.dagger - annotationProcessor libs.google.dagger.compiler + kapt libs.google.dagger.compiler // Database Library (https://github.com/agrosner/DBFlow) implementation libs.dbflow.core implementation libs.dbflow.lib - annotationProcessor libs.dbflow.processor + kapt libs.dbflow.processor // Gson Serialization Library (https://github.com/google/gson) implementation libs.google.gson From 1f813aff981b6ec20b60864e1dbea9afa5942504 Mon Sep 17 00:00:00 2001 From: Norbel Ambanumben Date: Sat, 21 Oct 2023 17:50:30 +0100 Subject: [PATCH 04/50] AGP upgrade --- .github/workflows/archive.yml | 4 ++-- .github/workflows/build.yml | 2 +- .github/workflows/emulator.yml | 2 +- .github/workflows/test.yml | 2 +- app/build.gradle | 6 +++--- app/jacoco.gradle | 20 +++++++++++++------- gradle.properties | 3 +++ gradle/libs.versions.toml | 6 +++--- gradle/wrapper/gradle-wrapper.properties | 2 +- shared-test/build.gradle | 11 ++++++++--- 10 files changed, 36 insertions(+), 22 deletions(-) diff --git a/.github/workflows/archive.yml b/.github/workflows/archive.yml index d41537923..b04392ac3 100644 --- a/.github/workflows/archive.yml +++ b/.github/workflows/archive.yml @@ -7,7 +7,7 @@ jobs: steps: - uses: actions/setup-java@v2 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: checkout uses: actions/checkout@v2 @@ -16,4 +16,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: dev-apk - path: app/build/outputs/apk/devFull/release \ No newline at end of file + path: app/build/outputs/apk/devFull/release diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ed6c7a7e..46f49645d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/setup-java@v2 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: checkout uses: actions/checkout@v2 diff --git a/.github/workflows/emulator.yml b/.github/workflows/emulator.yml index 0a599c15a..de92c9393 100644 --- a/.github/workflows/emulator.yml +++ b/.github/workflows/emulator.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/setup-java@v2 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: checkout uses: actions/checkout@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e678fedd6..f743fca58 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: steps: - uses: actions/setup-java@v2 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: checkout uses: actions/checkout@v2 diff --git a/app/build.gradle b/app/build.gradle index ade32bc44..1c215b084 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -86,11 +86,11 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8 + jvmTarget = JavaVersion.VERSION_17 } buildFeatures { viewBinding = true diff --git a/app/jacoco.gradle b/app/jacoco.gradle index f75812752..15b2b36c1 100644 --- a/app/jacoco.gradle +++ b/app/jacoco.gradle @@ -48,13 +48,19 @@ task jacocoAndroidTestReport(type: JacocoReport) { executionData.from += fileTree(dir: codeCoverageDataLocation, includes: ['**/*.ec']) } - reports { - html.enabled true - html.destination file("${buildDir}/reports/coverage") - xml.enabled true - xml.destination file("${buildDir}/reports/coverage.xml") - csv.enabled false - } + reports { + html { + enabled true + destination file("${buildDir}/reports/coverage") + } + xml { + enabled true + destination file("${buildDir}/reports/coverage.xml") + } + csv { + enabled false + } + } doLast { println "Wrote HTML coverage report to ${reports.html.destination}/index.html" diff --git a/gradle.properties b/gradle.properties index 8de505811..a8c570082 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,10 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. +android.defaults.buildfeatures.buildconfig=true android.enableJetifier=true +android.nonFinalResIds=false +android.nonTransitiveRClass=false android.useAndroidX=true org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 45a0cb6c7..4c1c32406 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -androidGradlePlugin = "7.4.1" +androidGradlePlugin = "8.1.2" barista = "3.9.0" countlySdk = "21.11.0" faker = "1.2.8" @@ -10,7 +10,7 @@ fastlaneScreengrab = "2.0.0" sentryAndroid = "6.3.0" xanscaleLocalhostToolkit = "19.05.01" commonsIo = "2.6" -jacoco = "0.8.5" +jacoco = "0.8.7" kotlin = "1.8.0" # Android X @@ -29,7 +29,7 @@ androidxEspressoCore = "3.5.1" googleGson = "2.8.9" googleGuava = "30.1.1-android" googleMaterial = "1.6.1" -googleDagger = "2.44.2" +googleDagger = "2.45" googleFirebaseBon = "26.3.0" googlePlaycore = "1.10.3" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2ec77e51a..3a0290794 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/shared-test/build.gradle b/shared-test/build.gradle index 1877880b9..34d70a978 100644 --- a/shared-test/build.gradle +++ b/shared-test/build.gradle @@ -6,13 +6,17 @@ plugins { android { namespace 'org.openobservatory.ooniprobe.shared.test' - compileSdk 33 + compileSdk libs.versions.compileSdk.get().toInteger() defaultConfig { - minSdk 21 + minSdk libs.versions.minSdk.get().toInteger() } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8 + jvmTarget = JavaVersion.VERSION_17 } flavorDimensions = ['testing', 'license'] productFlavors { @@ -60,6 +64,7 @@ dependencies { implementation libs.retrofit.logging.interceptor implementation libs.androidx.appcompat + implementation libs.xanscale.localhost.toolkit testImplementation libs.junit4 androidTestImplementation libs.androidx.junit From 9b41077e89a6d1d4463c57475462c1380ec601bd Mon Sep 17 00:00:00 2001 From: Norbel Ambanumben Date: Sat, 21 Oct 2023 18:17:45 +0100 Subject: [PATCH 05/50] Updated roboelectric --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4c1c32406..9759f49f3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ countlySdk = "21.11.0" faker = "1.2.8" mockitoCore = "5.3.1" mockitoInline = "4.6.1" -robolectric = "4.6.1" +robolectric = "4.10.3" fastlaneScreengrab = "2.0.0" sentryAndroid = "6.3.0" xanscaleLocalhostToolkit = "19.05.01" From 66ffbc30a11a357616bd61d909aaed57451f3b6d Mon Sep 17 00:00:00 2001 From: Norbel Ambanumben Date: Tue, 24 Oct 2023 21:50:49 +0100 Subject: [PATCH 06/50] Upgrade gradle to `8.1.2` and add support for kotlin and `kapt` --- .github/workflows/archive.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/emulator.yml | 2 +- .github/workflows/test.yml | 2 +- app/build.gradle | 18 +++++++++++------- app/jacoco.gradle | 20 +++++++++++++------- gradle.properties | 3 +++ gradle/libs.versions.toml | 8 ++++---- gradle/wrapper/gradle-wrapper.properties | 2 +- shared-test/build.gradle | 21 +++++++++++++-------- 10 files changed, 49 insertions(+), 31 deletions(-) diff --git a/.github/workflows/archive.yml b/.github/workflows/archive.yml index d41537923..40344f610 100644 --- a/.github/workflows/archive.yml +++ b/.github/workflows/archive.yml @@ -7,7 +7,7 @@ jobs: steps: - uses: actions/setup-java@v2 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: checkout uses: actions/checkout@v2 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ed6c7a7e..46f49645d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/setup-java@v2 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: checkout uses: actions/checkout@v2 diff --git a/.github/workflows/emulator.yml b/.github/workflows/emulator.yml index 0a599c15a..de92c9393 100644 --- a/.github/workflows/emulator.yml +++ b/.github/workflows/emulator.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/setup-java@v2 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: checkout uses: actions/checkout@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e678fedd6..f743fca58 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: steps: - uses: actions/setup-java@v2 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: checkout uses: actions/checkout@v2 diff --git a/app/build.gradle b/app/build.gradle index 5abfe08e1..1c215b084 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' - id 'org.jetbrains.kotlin.android' + id 'kotlin-android' + id 'kotlin-kapt' } apply from: 'jacoco.gradle' @@ -85,8 +86,11 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17 } buildFeatures { viewBinding = true @@ -111,7 +115,7 @@ dependencies { implementation libs.google.gson // Third-party - annotationProcessor libs.dbflow.processor + kapt libs.dbflow.processor implementation libs.dbflow.core implementation libs.dbflow.lib @@ -136,7 +140,7 @@ dependencies { // Dependency Injection implementation libs.google.dagger - annotationProcessor libs.google.dagger.compiler + kapt libs.google.dagger.compiler // Logger implementation project(':applogger') @@ -153,7 +157,7 @@ dependencies { testImplementation libs.robolectric testImplementation libs.faker testImplementation libs.ooni.oonimkall - testAnnotationProcessor libs.google.dagger.compiler + kaptTest libs.google.dagger.compiler // Instrumentation Testing androidTestImplementation project(':shared-test') @@ -166,7 +170,7 @@ dependencies { androidTestImplementation libs.androidx.espresso.contrib androidTestImplementation libs.androidx.espresso.core androidTestImplementation libs.barista - androidTestAnnotationProcessor libs.google.dagger.compiler + kaptAndroidTest libs.google.dagger.compiler } static def versionCodeDate() { diff --git a/app/jacoco.gradle b/app/jacoco.gradle index f75812752..15b2b36c1 100644 --- a/app/jacoco.gradle +++ b/app/jacoco.gradle @@ -48,13 +48,19 @@ task jacocoAndroidTestReport(type: JacocoReport) { executionData.from += fileTree(dir: codeCoverageDataLocation, includes: ['**/*.ec']) } - reports { - html.enabled true - html.destination file("${buildDir}/reports/coverage") - xml.enabled true - xml.destination file("${buildDir}/reports/coverage.xml") - csv.enabled false - } + reports { + html { + enabled true + destination file("${buildDir}/reports/coverage") + } + xml { + enabled true + destination file("${buildDir}/reports/coverage.xml") + } + csv { + enabled false + } + } doLast { println "Wrote HTML coverage report to ${reports.html.destination}/index.html" diff --git a/gradle.properties b/gradle.properties index 8de505811..a8c570082 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,10 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. +android.defaults.buildfeatures.buildconfig=true android.enableJetifier=true +android.nonFinalResIds=false +android.nonTransitiveRClass=false android.useAndroidX=true org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e1737e670..df1da5b9d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,16 +1,16 @@ [versions] -androidGradlePlugin = "7.4.1" +androidGradlePlugin = "8.1.2" barista = "3.9.0" countlySdk = "23.6.0" faker = "1.2.8" mockitoCore = "5.3.1" mockitoInline = "4.6.1" -robolectric = "4.6.1" +robolectric = "4.10.3" fastlaneScreengrab = "2.0.0" sentryAndroid = "6.3.0" xanscaleLocalhostToolkit = "19.05.01" commonsIo = "2.6" -jacoco = "0.8.5" +jacoco = "0.8.7" kotlin = "1.8.0" # Android X @@ -29,7 +29,7 @@ androidxEspressoCore = "3.5.1" googleGson = "2.8.9" googleGuava = "30.1.1-android" googleMaterial = "1.6.1" -googleDagger = "2.36" +googleDagger = "2.45" googleFirebaseBon = "26.3.0" googlePlaycore = "1.10.3" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2ec77e51a..3a0290794 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/shared-test/build.gradle b/shared-test/build.gradle index 607a28dff..34d70a978 100644 --- a/shared-test/build.gradle +++ b/shared-test/build.gradle @@ -1,18 +1,22 @@ plugins { id 'com.android.library' + id 'kotlin-android' + id 'kotlin-kapt' } android { namespace 'org.openobservatory.ooniprobe.shared.test' - compileSdk 33 + compileSdk libs.versions.compileSdk.get().toInteger() defaultConfig { - minSdk 21 + minSdk libs.versions.minSdk.get().toInteger() } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17 } flavorDimensions = ['testing', 'license'] productFlavors { @@ -41,12 +45,12 @@ dependencies { // Dependency Injection (https://dagger.dev/) implementation libs.google.dagger - annotationProcessor libs.google.dagger.compiler + kapt libs.google.dagger.compiler // Database Library (https://github.com/agrosner/DBFlow) implementation libs.dbflow.core implementation libs.dbflow.lib - annotationProcessor libs.dbflow.processor + kapt libs.dbflow.processor // Gson Serialization Library (https://github.com/google/gson) implementation libs.google.gson @@ -60,6 +64,7 @@ dependencies { implementation libs.retrofit.logging.interceptor implementation libs.androidx.appcompat + implementation libs.xanscale.localhost.toolkit testImplementation libs.junit4 androidTestImplementation libs.androidx.junit From 09d2685934adc3a97a2ec91543c21a80a26c8628 Mon Sep 17 00:00:00 2001 From: Norbel Ambanumben Date: Wed, 25 Oct 2023 18:49:40 +0100 Subject: [PATCH 07/50] Update from `ru.noties:markwon:2.0.1` to `io.noties.markwon:core:4.6.2` --- .../ooniprobe/activity/MeasurementDetailActivity.java | 6 ++++-- .../ooniprobe/activity/OverviewActivity.java | 7 ++++--- .../openobservatory/ooniprobe/activity/ProxyActivity.java | 6 ++++-- .../ooniprobe/fragment/measurement/PsiphonFragment.java | 7 +++++-- .../ooniprobe/fragment/measurement/RiseupVPNFragment.java | 7 +++++-- .../ooniprobe/fragment/measurement/TorFragment.java | 7 +++++-- .../fragment/measurement/WebConnectivityFragment.java | 8 +++++--- .../fragment/onboarding/Onboarding3Fragment.java | 6 ++++-- gradle/libs.versions.toml | 4 ++-- 9 files changed, 38 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/MeasurementDetailActivity.java b/app/src/main/java/org/openobservatory/ooniprobe/activity/MeasurementDetailActivity.java index 91bdc4dfd..9eada3e2d 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/MeasurementDetailActivity.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/MeasurementDetailActivity.java @@ -27,7 +27,7 @@ import org.openobservatory.ooniprobe.model.database.Network; import org.openobservatory.ooniprobe.test.suite.PerformanceSuite; import org.openobservatory.ooniprobe.test.test.*; -import ru.noties.markwon.Markwon; +import io.noties.markwon.Markwon; import javax.inject.Inject; import java.io.Serializable; @@ -212,7 +212,9 @@ public void onError(String msg) { binding.log.setVisibility(View.GONE); if (!measurementsManager.hasReportId(measurement)) binding.explorer.setVisibility(View.GONE); - Markwon.setMarkdown(binding.methodology, getString(R.string.TestResults_Details_Methodology_Paragraph, getString(measurement.getTest().getUrlResId()))); + Markwon.builder(this) + .build() + .setMarkdown(binding.methodology, getString(R.string.TestResults_Details_Methodology_Paragraph, getString(measurement.getTest().getUrlResId()))); load(); binding.log.setOnClickListener(v -> logClick()); binding.data.setOnClickListener(v -> dataClick()); 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 60d418a96..9d1b79018 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java @@ -23,7 +23,7 @@ import javax.inject.Inject; -import ru.noties.markwon.Markwon; +import io.noties.markwon.Markwon; public class OverviewActivity extends AbstractActivity { private static final String TEST = "test"; @@ -54,6 +54,7 @@ public static Intent newIntent(Context context, AbstractSuite testSuite) { binding.run.setAlpha(0.5F); binding.run.setEnabled(false); } + Markwon markwon = Markwon.builder(this).build(); if (testSuite.getName().equals(ExperimentalSuite.NAME)) { String experimentalLinks = "\n\n* [STUN Reachability](https://github.com/ooni/spec/blob/master/nettests/ts-025-stun-reachability.md)" + @@ -61,12 +62,12 @@ public static Intent newIntent(Context context, AbstractSuite testSuite) { "\n\n* [ECH Check](https://github.com/ooni/spec/blob/master/nettests/ts-039-echcheck.md)" + "\n\n* [Tor Snowflake](https://ooni.org/nettest/tor-snowflake/) "+ String.format(" ( %s )",getString(R.string.Settings_TestOptions_LongRunningTest))+ "\n\n* [Vanilla Tor](https://github.com/ooni/spec/blob/master/nettests/ts-016-vanilla-tor.md) " + String.format(" ( %s )",getString(R.string.Settings_TestOptions_LongRunningTest)); - Markwon.setMarkdown(binding.desc, getString(testSuite.getDesc1(), experimentalLinks)); + markwon.setMarkdown(binding.desc, getString(testSuite.getDesc1(), experimentalLinks)); if (TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL) binding.desc.setTextDirection(View.TEXT_DIRECTION_RTL); } else - Markwon.setMarkdown(binding.desc, getString(testSuite.getDesc1())); + markwon.setMarkdown(binding.desc, getString(testSuite.getDesc1())); Result lastResult = Result.getLastResult(testSuite.getName()); if (lastResult == null) binding.lastTime.setText(R.string.Dashboard_Overview_LastRun_Never); diff --git a/app/src/main/java/org/openobservatory/ooniprobe/activity/ProxyActivity.java b/app/src/main/java/org/openobservatory/ooniprobe/activity/ProxyActivity.java index 902fec4c8..fdc5bb245 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/ProxyActivity.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/ProxyActivity.java @@ -14,7 +14,7 @@ import org.openobservatory.ooniprobe.common.ProxyProtocol; import org.openobservatory.ooniprobe.common.ProxySettings; import org.openobservatory.ooniprobe.databinding.ActivityProxyBinding; -import ru.noties.markwon.Markwon; +import io.noties.markwon.Markwon; import javax.inject.Inject; import java.net.URISyntaxException; @@ -119,7 +119,9 @@ public void onCreate(Bundle savedInstanceState) { setContentView(binding.getRoot()); // We fill the footer that helps users to understand this settings screen. - Markwon.setMarkdown(binding.proxyFooter, getString(R.string.Settings_Proxy_Footer)); + Markwon.builder(this) + .build() + .setMarkdown(binding.proxyFooter, getString(R.string.Settings_Proxy_Footer)); // We read settings and configure the initial view. loadSettingsAndConfigureInitialView(); diff --git a/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/PsiphonFragment.java b/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/PsiphonFragment.java index ae697cff3..f1e51559a 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/PsiphonFragment.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/PsiphonFragment.java @@ -10,7 +10,8 @@ import org.openobservatory.ooniprobe.R; import org.openobservatory.ooniprobe.databinding.FragmentMeasurementPsiphonBinding; import org.openobservatory.ooniprobe.model.database.Measurement; -import ru.noties.markwon.Markwon; + +import io.noties.markwon.Markwon; public class PsiphonFragment extends Fragment { private static final String MEASUREMENT = "measurement"; @@ -28,7 +29,9 @@ public static PsiphonFragment newInstance(Measurement measurement) { Measurement measurement = (Measurement) getArguments().getSerializable(MEASUREMENT); assert measurement != null; FragmentMeasurementPsiphonBinding binding = FragmentMeasurementPsiphonBinding.inflate(inflater,container,false); - Markwon.setMarkdown(binding.desc, + Markwon.builder(getContext()) + .build() + .setMarkdown(binding.desc, measurement.is_anomaly ? getString(R.string.TestResults_Details_Circumvention_Psiphon_Blocked_Content_Paragraph) : getString(R.string.TestResults_Details_Circumvention_Psiphon_Reachable_Content_Paragraph) diff --git a/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/RiseupVPNFragment.java b/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/RiseupVPNFragment.java index 83670bd5e..32401d0da 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/RiseupVPNFragment.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/RiseupVPNFragment.java @@ -10,7 +10,8 @@ import org.openobservatory.ooniprobe.R; import org.openobservatory.ooniprobe.databinding.FragmentMeasurementRiseupvpnBinding; import org.openobservatory.ooniprobe.model.database.Measurement; -import ru.noties.markwon.Markwon; + +import io.noties.markwon.Markwon; public class RiseupVPNFragment extends Fragment { private static final String MEASUREMENT = "measurement"; @@ -28,7 +29,9 @@ public static RiseupVPNFragment newInstance(Measurement measurement) { Measurement measurement = (Measurement) getArguments().getSerializable(MEASUREMENT); assert measurement != null; FragmentMeasurementRiseupvpnBinding binding = FragmentMeasurementRiseupvpnBinding.inflate(inflater,container,false); - Markwon.setMarkdown(binding.desc, + Markwon.builder(getContext()) + .build() + .setMarkdown(binding.desc, measurement.is_anomaly ? getString(R.string.TestResults_Details_Circumvention_RiseupVPN_Blocked_Content_Paragraph) : getString(R.string.TestResults_Details_Circumvention_RiseupVPN_Reachable_Content_Paragraph) diff --git a/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/TorFragment.java b/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/TorFragment.java index 3b47b9dcd..d859d2eca 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/TorFragment.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/TorFragment.java @@ -10,7 +10,8 @@ import org.openobservatory.ooniprobe.R; import org.openobservatory.ooniprobe.databinding.FragmentMeasurementTorBinding; import org.openobservatory.ooniprobe.model.database.Measurement; -import ru.noties.markwon.Markwon; + +import io.noties.markwon.Markwon; public class TorFragment extends Fragment { private static final String MEASUREMENT = "measurement"; @@ -30,7 +31,9 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c Measurement measurement = (Measurement) getArguments().getSerializable(MEASUREMENT); assert measurement != null; FragmentMeasurementTorBinding binding = FragmentMeasurementTorBinding.inflate(inflater,container,false); - Markwon.setMarkdown(binding.desc, + Markwon.builder(getContext()) + .build() + .setMarkdown(binding.desc, measurement.is_anomaly ? getString(R.string.TestResults_Details_Circumvention_Tor_Blocked_Content_Paragraph) : getString(R.string.TestResults_Details_Circumvention_Tor_Reachable_Content_Paragraph) diff --git a/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/WebConnectivityFragment.java b/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/WebConnectivityFragment.java index e39131b45..6951ada06 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/WebConnectivityFragment.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/fragment/measurement/WebConnectivityFragment.java @@ -10,7 +10,8 @@ import org.openobservatory.ooniprobe.R; import org.openobservatory.ooniprobe.databinding.FragmentMeasurementWebconnectivityBinding; import org.openobservatory.ooniprobe.model.database.Measurement; -import ru.noties.markwon.Markwon; + +import io.noties.markwon.Markwon; public class WebConnectivityFragment extends Fragment { private static final String MEASUREMENT = "measurement"; @@ -30,10 +31,11 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c Measurement measurement = (Measurement) getArguments().getSerializable(MEASUREMENT); assert measurement != null; FragmentMeasurementWebconnectivityBinding binding = FragmentMeasurementWebconnectivityBinding.inflate(inflater,container,false); + Markwon markwon = Markwon.builder(getContext()).build(); if (measurement.is_anomaly) - Markwon.setMarkdown(binding.desc, getString(R.string.TestResults_Details_Websites_LikelyBlocked_Content_Paragraph, measurement.url.url, getString(measurement.getTestKeys().getWebsiteBlocking()))); + markwon.setMarkdown(binding.desc, getString(R.string.TestResults_Details_Websites_LikelyBlocked_Content_Paragraph, measurement.url.url, getString(measurement.getTestKeys().getWebsiteBlocking()))); else - Markwon.setMarkdown(binding.desc, getString(R.string.TestResults_Details_Websites_Reachable_Content_Paragraph, measurement.url.url)); + markwon.setMarkdown(binding.desc, getString(R.string.TestResults_Details_Websites_Reachable_Content_Paragraph, measurement.url.url)); return binding.getRoot(); } } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/fragment/onboarding/Onboarding3Fragment.java b/app/src/main/java/org/openobservatory/ooniprobe/fragment/onboarding/Onboarding3Fragment.java index 005204e37..3fe085d54 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/fragment/onboarding/Onboarding3Fragment.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/fragment/onboarding/Onboarding3Fragment.java @@ -14,7 +14,7 @@ import org.openobservatory.ooniprobe.common.ThirdPartyServices; import org.openobservatory.ooniprobe.common.service.ServiceUtil; import org.openobservatory.ooniprobe.databinding.FragmentOnboarding3Binding; -import ru.noties.markwon.Markwon; +import io.noties.markwon.Markwon; import javax.inject.Inject; @@ -31,7 +31,9 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c binding.bullet1.setText(getString(R.string.bullet, getString(R.string.Onboarding_DefaultSettings_Bullet_1))); binding.bullet2.setText(getString(R.string.bullet, getString(R.string.Onboarding_DefaultSettings_Bullet_2))); binding.bullet3.setText(getString(R.string.bullet, getString(R.string.Onboarding_DefaultSettings_Bullet_3))); - Markwon.setMarkdown(binding.paragraph, getString(R.string.Onboarding_DefaultSettings_Paragraph)); + Markwon.builder(getContext()) + .build() + .setMarkdown(binding.paragraph, getString(R.string.Onboarding_DefaultSettings_Paragraph)); binding.master.setOnClickListener(v -> masterClick()); binding.slave.setOnClickListener(v -> slaveClick()); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e1737e670..5d6e009d8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,7 +36,7 @@ googlePlaycore = "1.10.3" # OONI compileSdk = "34" lottie = "3.0.7" -markwon = "2.0.1" +markwon = "4.6.2" shapeofview = "1.3.2" targetSdk = "33" minSdk = "21" @@ -69,7 +69,7 @@ mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockitoCore mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockitoInline" } lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" } -markwon = { module = "ru.noties:markwon", version.ref = "markwon" } +markwon = { module = "io.noties.markwon:core", version.ref = "markwon" } retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofitCore" } retrofit-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "retrofitLoggingInterceptor" } retrofit-lib = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofitCore" } From 9353e643bdc127d45a5befa537c70fa08df86778 Mon Sep 17 00:00:00 2001 From: Norbel Ambanumben Date: Wed, 25 Oct 2023 19:02:59 +0100 Subject: [PATCH 08/50] Change dependency naming --- app/build.gradle | 2 +- gradle/libs.versions.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5abfe08e1..54ba15f1e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -122,7 +122,7 @@ dependencies { implementation libs.xanscale.localhost.toolkit implementation libs.lottie - implementation libs.markwon + implementation libs.markwon.core implementation libs.commons.io //arcview to fragment_dashboard implementation libs.shapeofview diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5d6e009d8..a58947f9d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -69,7 +69,7 @@ mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockitoCore mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockitoInline" } lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" } -markwon = { module = "io.noties.markwon:core", version.ref = "markwon" } +markwon-core = { module = "io.noties.markwon:core", version.ref = "markwon" } retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofitCore" } retrofit-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "retrofitLoggingInterceptor" } retrofit-lib = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofitCore" } From 6fd4edf0a4a93ab54d15b4ba067697cdf42b227d Mon Sep 17 00:00:00 2001 From: Norbel Ambanumben Date: Wed, 25 Oct 2023 20:35:24 +0100 Subject: [PATCH 09/50] Updated `OverviewActivity.` and added `ReadMorePlugin` --- .../ooniprobe/activity/OverviewActivity.java | 16 +- .../ooniprobe/common/ReadMorePlugin.kt | 144 ++++++++++++++++++ app/src/main/res/layout/activity_overview.xml | 13 -- gradle/wrapper/gradle-wrapper.properties | 3 +- 4 files changed, 150 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/org/openobservatory/ooniprobe/common/ReadMorePlugin.kt 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 9d1b79018..782f6ee71 100644 --- a/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java +++ b/app/src/main/java/org/openobservatory/ooniprobe/activity/OverviewActivity.java @@ -12,6 +12,7 @@ import org.openobservatory.ooniprobe.R; import org.openobservatory.ooniprobe.common.PreferenceManager; +import org.openobservatory.ooniprobe.common.ReadMorePlugin; import org.openobservatory.ooniprobe.databinding.ActivityOverviewBinding; import org.openobservatory.ooniprobe.model.database.Result; import org.openobservatory.ooniprobe.test.suite.AbstractSuite; @@ -50,11 +51,9 @@ public static Intent newIntent(Context context, AbstractSuite testSuite) { setTitle(testSuite.getTitle()); binding.icon.setImageResource(testSuite.getIcon()); binding.customUrl.setVisibility(testSuite.getName().equals(WebsitesSuite.NAME) ? View.VISIBLE : View.GONE); - if(testSuite.isTestEmpty(preferenceManager)){ - binding.run.setAlpha(0.5F); - binding.run.setEnabled(false); - } - Markwon markwon = Markwon.builder(this).build(); + Markwon markwon = Markwon.builder(this) + .usePlugin(new ReadMorePlugin()) + .build(); if (testSuite.getName().equals(ExperimentalSuite.NAME)) { String experimentalLinks = "\n\n* [STUN Reachability](https://github.com/ooni/spec/blob/master/nettests/ts-025-stun-reachability.md)" + @@ -78,7 +77,6 @@ public static Intent newIntent(Context context, AbstractSuite testSuite) { } private void setUpOnCLickListeners() { - binding.run.setOnClickListener(view -> onRunClick()); binding.customUrl.setOnClickListener(view -> customUrlClick()); } @@ -95,12 +93,6 @@ public boolean onSupportNavigateUp() { return true; } - void onRunClick() { - if(!testSuite.isTestEmpty(preferenceManager)){ - RunningActivity.runAsForegroundService(this, testSuite.asArray(), this::bindTestService, preferenceManager); - } - } - void customUrlClick() { startActivity(new Intent(this, CustomWebsiteActivity.class)); } diff --git a/app/src/main/java/org/openobservatory/ooniprobe/common/ReadMorePlugin.kt b/app/src/main/java/org/openobservatory/ooniprobe/common/ReadMorePlugin.kt new file mode 100644 index 000000000..4041f6958 --- /dev/null +++ b/app/src/main/java/org/openobservatory/ooniprobe/common/ReadMorePlugin.kt @@ -0,0 +1,144 @@ +package org.openobservatory.ooniprobe.common + +import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.style.ClickableSpan +import android.text.style.ReplacementSpan +import android.view.View +import android.widget.TextView +import io.noties.markwon.AbstractMarkwonPlugin + + +/** + * Read more plugin based on text length. + * @see ReadMorePluginSample + */ +class ReadMorePlugin : AbstractMarkwonPlugin() { + private val maxLength = 150 + private val labelMore = "\n\nRead more >" + private val labelLess = "\n\nRead less >" + + override fun afterSetText(textView: TextView) { + val text = textView.text + if (text.length < maxLength) { + // everything is OK, no need to ellipsize) + return + } + val breakAt = breakTextAt(text, 0, maxLength) + val cs = createCollapsedString(text, 0, breakAt) + textView.text = cs + } + + private fun createCollapsedString(text: CharSequence, start: Int, end: Int): CharSequence { + val builder = SpannableStringBuilder(text, start, end) + + // NB! each table row is represented as a space character and new-line (so length=2) no + // matter how many characters are inside table cells + + // we can _clean_ this builder, for example remove all dynamic content (like images and tables, + // but keep them in full/expanded version) + if (true) { + // it is an implementation detail but _mostly_ dynamic content is implemented as + // ReplacementSpans + val spans = builder.getSpans( + 0, builder.length, + ReplacementSpan::class.java + ) + if (spans != null) { + for (span in spans) { + builder.removeSpan(span) + } + } + + // NB! if there will be a table in _preview_ (collapsed) then each row will be represented as a + // space and new-line + trim(builder) + } + val fullText = createFullText(text, builder) + builder.append(" ...") + val length = builder.length + builder.append(labelMore) + builder.setSpan(object : ClickableSpan() { + override fun onClick(widget: View) { + (widget as TextView).text = fullText + } + }, length, builder.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + return builder + } + + private fun createFullText(text: CharSequence, collapsedText: CharSequence): CharSequence { + // full/expanded text can also be different, + // for example it can be kept as-is and have no `collapse` functionality (once expanded cannot collapse) + // or can contain collapse feature + val fullText: CharSequence + if (true) { + // for example, let's allow collapsing + val builder = SpannableStringBuilder(text) + builder.append(' ') + val length = builder.length + builder.append(labelLess) + builder.setSpan(object : ClickableSpan() { + override fun onClick(widget: View) { + (widget as TextView).text = collapsedText + } + }, length, builder.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + fullText = builder + } else { + fullText = text + } + return fullText + } + + companion object { + private fun trim(builder: SpannableStringBuilder) { + + // NB! tables use `\u00a0` (non breaking space) which is not reported as white-space + var c: Char + run { + var i = 0 + val length = builder.length + while (i < length) { + c = builder[i] + if (!Character.isWhitespace(c) && c != '\u00a0') { + if (i > 0) { + builder.replace(0, i, "") + } + break + } + i++ + } + } + for (i in builder.length - 1 downTo 0) { + c = builder[i] + if (!Character.isWhitespace(c) && c != '\u00a0') { + if (i < builder.length - 1) { + builder.replace(i, builder.length, "") + } + break + } + } + } + + // depending on your locale these can be different + // There is a BreakIterator in Android, but it is not reliable, still theoretically + // it should work better than hand-written and hardcoded rules + private fun breakTextAt(text: CharSequence, start: Int, max: Int): Int { + var last = start + + // no need to check for _start_ (anyway will be ignored) + for (i in start + max - 1 downTo start + 1) { + val c = text[i] + if (Character.isWhitespace(c) || c == '.' || c == ',' || c == '!' || c == '?') { + // include this special character + last = i - 1 + break + } + } + return if (last <= start) { + // when used in subSequence last index is exclusive, + // so given max=150 would result in 0-149 subSequence + start + max + } else last + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_overview.xml b/app/src/main/res/layout/activity_overview.xml index e4c1b1665..6a642377b 100644 --- a/app/src/main/res/layout/activity_overview.xml +++ b/app/src/main/res/layout/activity_overview.xml @@ -75,19 +75,6 @@ -