From a8e83a03b1da4cebad12165bd78e53f7c0cf3e4e Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Fri, 12 Mar 2021 17:53:19 -0600 Subject: [PATCH] Convert attribution activity to compose --- .../attribution/AttributionActivity.kt | 64 +++++------------ .../attribution/AttributionAdapter.kt | 30 -------- .../activities/attribution/AttributionRow.kt | 22 ------ .../attribution/AttributionViewModel.kt | 63 +++++++---------- .../activities/attribution/LicenseHeader.kt | 22 ------ .../activities/attribution/LicenseRow.kt | 25 ------- .../java/org/tasks/compose/AttributionList.kt | 69 +++++++++++++++++++ .../main/res/layout/activity_attributions.xml | 14 ++-- app/src/main/res/layout/row_attribution.xml | 44 ------------ .../res/layout/row_attribution_header.xml | 18 ----- app/src/main/res/values/dimens.xml | 2 - app/src/main/res/values/styles.xml | 11 --- 12 files changed, 120 insertions(+), 264 deletions(-) delete mode 100644 app/src/main/java/org/tasks/activities/attribution/AttributionAdapter.kt delete mode 100644 app/src/main/java/org/tasks/activities/attribution/AttributionRow.kt delete mode 100644 app/src/main/java/org/tasks/activities/attribution/LicenseHeader.kt delete mode 100644 app/src/main/java/org/tasks/activities/attribution/LicenseRow.kt create mode 100644 app/src/main/java/org/tasks/compose/AttributionList.kt delete mode 100644 app/src/main/res/layout/row_attribution.xml delete mode 100644 app/src/main/res/layout/row_attribution_header.xml diff --git a/app/src/main/java/org/tasks/activities/attribution/AttributionActivity.kt b/app/src/main/java/org/tasks/activities/attribution/AttributionActivity.kt index 359138ef5..4b7f2a974 100644 --- a/app/src/main/java/org/tasks/activities/attribution/AttributionActivity.kt +++ b/app/src/main/java/org/tasks/activities/attribution/AttributionActivity.kt @@ -1,64 +1,36 @@ package org.tasks.activities.attribution import android.os.Bundle -import androidx.appcompat.widget.Toolbar -import androidx.lifecycle.ViewModelProvider -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import butterknife.BindView +import androidx.activity.viewModels import butterknife.ButterKnife -import com.google.common.collect.Multimaps +import com.google.android.material.composethemeadapter.MdcTheme import dagger.hilt.android.AndroidEntryPoint import org.tasks.R -import org.tasks.activities.attribution.AttributionViewModel.LibraryAttribution +import org.tasks.compose.AttributionList.AttributionList +import org.tasks.databinding.ActivityAttributionsBinding import org.tasks.injection.ThemedInjectingAppCompatActivity -import timber.log.Timber -import java.util.* @AndroidEntryPoint class AttributionActivity : ThemedInjectingAppCompatActivity() { - @BindView(R.id.toolbar) - lateinit var toolbar: Toolbar - - @BindView(R.id.list) - lateinit var recyclerView: RecyclerView + val viewModel: AttributionViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_attributions) + val binding = ActivityAttributionsBinding.inflate(layoutInflater) + setContentView(binding.root) ButterKnife.bind(this) - toolbar.setTitle(R.string.third_party_licenses) - toolbar.setNavigationIcon(R.drawable.ic_outline_arrow_back_24px) - toolbar.setNavigationOnClickListener { finish() } - themeColor.apply(toolbar) - recyclerView.layoutManager = LinearLayoutManager(this) - } - - override fun onResume() { - super.onResume() - ViewModelProvider(this) - .get(AttributionViewModel::class.java) - .observe(this, androidx.lifecycle.Observer { libraryAttributions: List? -> - updateAttributions(libraryAttributions!!) - }) - } - - private fun updateAttributions(libraryAttributions: List) { - val rows = ArrayList() - val byLicense = Multimaps.index(libraryAttributions) { it!!.license } - byLicense.keySet().sorted().forEach { license -> - rows.add(AttributionRow(license)) - rows.addAll(getRows(byLicense[license])) + with(binding.toolbar.toolbar) { + setTitle(R.string.third_party_licenses) + setNavigationIcon(R.drawable.ic_outline_arrow_back_24px) + setNavigationOnClickListener { finish() } + themeColor.apply(this) } - recyclerView.adapter = AttributionAdapter(rows) - Timber.d(libraryAttributions.toString()) - } - - private fun getRows(attributions: List): Iterable { - val byCopyrightHolder = Multimaps.index(attributions) { lib -> lib!!.copyrightHolder } - return byCopyrightHolder.keySet().sorted().map { - val libraries = byCopyrightHolder[it].map { a -> "\u2022 ${a.libraryName}"} - AttributionRow(it, libraries.sorted().joinToString("\n")) + viewModel.attributions.observe(this) { + binding.compose.setContent { + MdcTheme { + AttributionList(it) + } + } } } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/activities/attribution/AttributionAdapter.kt b/app/src/main/java/org/tasks/activities/attribution/AttributionAdapter.kt deleted file mode 100644 index 3da3a788d..000000000 --- a/app/src/main/java/org/tasks/activities/attribution/AttributionAdapter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.tasks.activities.attribution - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import org.tasks.R - -class AttributionAdapter internal constructor(private val rows: List) : RecyclerView.Adapter() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val inflater = LayoutInflater.from(parent.context) - return if (viewType == 0) { - LicenseHeader(inflater.inflate(R.layout.row_attribution_header, parent, false)) - } else { - LicenseRow(inflater.inflate(R.layout.row_attribution, parent, false)) - } - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - val row = rows[position] - if (getItemViewType(position) == 0) { - (holder as LicenseHeader).bind(row.license) - } else { - (holder as LicenseRow).bind(row.copyrightHolder, row.libraries) - } - } - - override fun getItemViewType(position: Int) = if (rows[position].isHeader) 0 else 1 - - override fun getItemCount() = rows.size -} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/activities/attribution/AttributionRow.kt b/app/src/main/java/org/tasks/activities/attribution/AttributionRow.kt deleted file mode 100644 index c042c3d82..000000000 --- a/app/src/main/java/org/tasks/activities/attribution/AttributionRow.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.tasks.activities.attribution - -class AttributionRow { - val isHeader: Boolean - val license: String? - val copyrightHolder: String? - val libraries: String? - - internal constructor(license: String?) { - this.license = license - isHeader = true - copyrightHolder = null - libraries = null - } - - internal constructor(copyrightHolder: String?, libraries: String?) { - this.copyrightHolder = copyrightHolder - this.libraries = libraries - isHeader = false - license = null - } -} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/activities/attribution/AttributionViewModel.kt b/app/src/main/java/org/tasks/activities/attribution/AttributionViewModel.kt index 70d1d2175..995c60f88 100644 --- a/app/src/main/java/org/tasks/activities/attribution/AttributionViewModel.kt +++ b/app/src/main/java/org/tasks/activities/attribution/AttributionViewModel.kt @@ -2,48 +2,38 @@ package org.tasks.activities.attribution import android.content.Context import androidx.annotation.Keep -import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Observer import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import com.google.gson.GsonBuilder -import io.reactivex.Single -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.schedulers.Schedulers -import timber.log.Timber +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.io.InputStreamReader import java.nio.charset.StandardCharsets - -class AttributionViewModel : ViewModel() { - private val attributions = MutableLiveData?>() - private val disposables = CompositeDisposable() - private var loaded = false - - fun observe(activity: AppCompatActivity, observer: Observer?>) { - attributions.observe(activity, observer) - load(activity) - } - - private fun load(context: Context) { - if (loaded) { - return - } - loaded = true - disposables.add( - Single.fromCallable { - val licenses = context.assets.open("licenses.json") - val reader = InputStreamReader(licenses, StandardCharsets.UTF_8) - val list = GsonBuilder().create().fromJson(reader, AttributionList::class.java) - list.libraries +import javax.inject.Inject + +@HiltViewModel +class AttributionViewModel @Inject constructor( + @ApplicationContext context: Context +): ViewModel() { + val attributions = MutableLiveData>>>() + + init { + viewModelScope.launch { + val licenses = withContext(Dispatchers.IO) { + context.assets.open("licenses.json") + } + val reader = InputStreamReader(licenses, StandardCharsets.UTF_8) + val list = GsonBuilder().create().fromJson(reader, AttributionList::class.java) + attributions.value = list.libraries!! + .groupBy { it.license!! }.toSortedMap() + .mapValues { (_, libraries) -> + libraries.groupBy { it.copyrightHolder!! }.toSortedMap() } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ value: List? -> attributions.setValue(value) }) { t: Throwable? -> Timber.e(t) }) - } - - override fun onCleared() { - disposables.dispose() + } } internal class AttributionList { @@ -56,6 +46,5 @@ class AttributionViewModel : ViewModel() { @get:Keep var license: String? = null var libraryName: String? = null - } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/activities/attribution/LicenseHeader.kt b/app/src/main/java/org/tasks/activities/attribution/LicenseHeader.kt deleted file mode 100644 index 8eba797f9..000000000 --- a/app/src/main/java/org/tasks/activities/attribution/LicenseHeader.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.tasks.activities.attribution - -import android.view.View -import android.widget.TextView -import androidx.recyclerview.widget.RecyclerView -import butterknife.BindView -import butterknife.ButterKnife -import org.tasks.R - -internal class LicenseHeader(itemView: View) : RecyclerView.ViewHolder(itemView) { - - @BindView(R.id.license_name) - lateinit var licenseName: TextView - - init { - ButterKnife.bind(this, itemView) - } - - fun bind(license: String?) { - licenseName.text = license - } -} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/activities/attribution/LicenseRow.kt b/app/src/main/java/org/tasks/activities/attribution/LicenseRow.kt deleted file mode 100644 index 7d9aef9b7..000000000 --- a/app/src/main/java/org/tasks/activities/attribution/LicenseRow.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.tasks.activities.attribution - -import android.view.View -import android.widget.TextView -import androidx.recyclerview.widget.RecyclerView -import butterknife.BindView -import butterknife.ButterKnife -import org.tasks.R - -internal class LicenseRow(itemView: View) : RecyclerView.ViewHolder(itemView) { - @BindView(R.id.copyright_holder) - lateinit var copyrightHolder: TextView - - @BindView(R.id.libraries) - lateinit var libraries: TextView - - init { - ButterKnife.bind(this, itemView) - } - - fun bind(copyrightHolder: String?, libraries: String?) { - this.copyrightHolder.text = copyrightHolder - this.libraries.text = libraries - } -} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/compose/AttributionList.kt b/app/src/main/java/org/tasks/compose/AttributionList.kt new file mode 100644 index 000000000..786bfc2cb --- /dev/null +++ b/app/src/main/java/org/tasks/compose/AttributionList.kt @@ -0,0 +1,69 @@ +package org.tasks.compose + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Card +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import org.tasks.R +import org.tasks.activities.attribution.AttributionViewModel.LibraryAttribution +import org.tasks.compose.Constants.KEYLINE_FIRST + +object AttributionList { + @Composable + fun AttributionList(licenses: Map>>) { + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + ) { + licenses.forEach { (license, libraries) -> + Text( + license, + style = MaterialTheme.typography.h6, + color = MaterialTheme.colors.onBackground, + modifier = Modifier.padding(KEYLINE_FIRST) + ) + libraries.forEach { (copyrightHolder, libraries) -> + LibraryCard(copyrightHolder, libraries) + } + } + } + } + + @Composable + fun LibraryCard(copyrightHolder: String, libraries: List) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(Constants.HALF_KEYLINE), + backgroundColor = colorResource(R.color.content_background), + ) { + Column( + modifier = Modifier.padding(KEYLINE_FIRST) + ) { + Text( + copyrightHolder, + style = MaterialTheme.typography.body1, + color = MaterialTheme.colors.secondary + ) + Spacer(Modifier.height(Constants.HALF_KEYLINE)) + libraries.forEach { + Text( + "\u2022 ${it.libraryName!!}", + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.onBackground + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_attributions.xml b/app/src/main/res/layout/activity_attributions.xml index 974a2caa5..3532c89b2 100644 --- a/app/src/main/res/layout/activity_attributions.xml +++ b/app/src/main/res/layout/activity_attributions.xml @@ -4,13 +4,13 @@ android:layout_height="match_parent" android:orientation="vertical"> - + - + \ No newline at end of file diff --git a/app/src/main/res/layout/row_attribution.xml b/app/src/main/res/layout/row_attribution.xml deleted file mode 100644 index 96b36de58..000000000 --- a/app/src/main/res/layout/row_attribution.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/layout/row_attribution_header.xml b/app/src/main/res/layout/row_attribution_header.xml deleted file mode 100644 index cb67c05a8..000000000 --- a/app/src/main/res/layout/row_attribution_header.xml +++ /dev/null @@ -1,18 +0,0 @@ - - diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 6954c5df6..e7951670a 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -33,8 +33,6 @@ 14sp 8dp - 2dp - 2sp 8dp diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index c930c9a5e..96705e58d 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -143,17 +143,6 @@ 24dp - -