Convert attribution activity to compose

pull/1412/head
Alex Baker 5 years ago
parent 0dbd0551d1
commit a8e83a03b1

@ -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)
with(binding.toolbar.toolbar) {
setTitle(R.string.third_party_licenses)
setNavigationIcon(R.drawable.ic_outline_arrow_back_24px)
setNavigationOnClickListener { finish() }
themeColor.apply(this)
}
override fun onResume() {
super.onResume()
ViewModelProvider(this)
.get(AttributionViewModel::class.java)
.observe(this, androidx.lifecycle.Observer { libraryAttributions: List<LibraryAttribution>? ->
updateAttributions(libraryAttributions!!)
})
viewModel.attributions.observe(this) {
binding.compose.setContent {
MdcTheme {
AttributionList(it)
}
private fun updateAttributions(libraryAttributions: List<LibraryAttribution>) {
val rows = ArrayList<AttributionRow>()
val byLicense = Multimaps.index(libraryAttributions) { it!!.license }
byLicense.keySet().sorted().forEach { license ->
rows.add(AttributionRow(license))
rows.addAll(getRows(byLicense[license]))
}
recyclerView.adapter = AttributionAdapter(rows)
Timber.d(libraryAttributions.toString())
}
private fun getRows(attributions: List<LibraryAttribution>): Iterable<AttributionRow> {
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"))
}
}
}

@ -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<AttributionRow>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
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
}

@ -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
}
}

@ -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<List<LibraryAttribution>?>()
private val disposables = CompositeDisposable()
private var loaded = false
fun observe(activity: AppCompatActivity, observer: Observer<List<LibraryAttribution>?>) {
attributions.observe(activity, observer)
load(activity)
}
private fun load(context: Context) {
if (loaded) {
return
import javax.inject.Inject
@HiltViewModel
class AttributionViewModel @Inject constructor(
@ApplicationContext context: Context
): ViewModel() {
val attributions = MutableLiveData<Map<String, Map<String, List<LibraryAttribution>>>>()
init {
viewModelScope.launch {
val licenses = withContext(Dispatchers.IO) {
context.assets.open("licenses.json")
}
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
attributions.value = list.libraries!!
.groupBy { it.license!! }.toSortedMap()
.mapValues { (_, libraries) ->
libraries.groupBy { it.copyrightHolder!! }.toSortedMap()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ value: List<LibraryAttribution>? -> 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
}
}

@ -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
}
}

@ -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
}
}

@ -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<String, Map<String, List<LibraryAttribution>>>) {
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<LibraryAttribution>) {
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
)
}
}
}
}
}

@ -4,13 +4,13 @@
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar"/>
<include
android:id="@+id/toolbar"
layout="@layout/toolbar" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground"
android:gravity="center"/>
android:layout_height="match_parent" />
</LinearLayout>

@ -1,44 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/CardViewStyle"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
app:cardMaxElevation="1dp"
app:cardElevation="1dp"
app:cardBackgroundColor="@color/content_background">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:paddingTop="@dimen/keyline_first"
android:paddingBottom="@dimen/keyline_first"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/copyright_holder"
android:textStyle="bold"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/keyline_first"
android:paddingEnd="@dimen/keyline_first"
android:gravity="center_vertical"
android:ellipsize="end"
android:maxLines="1"
android:textSize="@dimen/sku_details_row_text_size"/>
<TextView
android:id="@+id/libraries"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/keyline_first"
android:paddingEnd="@dimen/keyline_first"
android:textColor="?android:textColorSecondary"
android:textSize="@dimen/sku_details_row_text_size"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/license_name"
style="@style/TextAppearance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyline_first"
android:layout_marginBottom="@dimen/keyline_first"
android:layout_marginStart="@dimen/card_view_margin"
android:layout_marginEnd="@dimen/card_view_margin"
android:paddingStart="@dimen/keyline_first"
android:paddingEnd="@dimen/keyline_first"
android:gravity="start|center_vertical"
android:textColor="?attr/colorAccent"
android:textSize="@dimen/sku_details_row_text_size"
app:fontFamily="sans-serif-medium"/>

@ -33,8 +33,6 @@
<dimen name="sku_details_row_text_size">14sp</dimen>
<dimen name="card_view_margin">8dp</dimen>
<dimen name="card_view_card_corner_radius">2dp</dimen>
<dimen name="card_view_card_elevation">2sp</dimen>
<dimen name="header_gap">8dp</dimen>

@ -143,17 +143,6 @@
<item name="android:layout_height">24dp</item>
</style>
<style name="CardViewStyle" parent="Widget.MaterialComponents.CardView">
<item name="android:layout_marginTop">0dp</item>
<item name="android:layout_marginBottom">0dp</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_marginEnd">@dimen/card_view_margin</item>
<item name="android:layout_marginStart">@dimen/card_view_margin</item>
<item name="cardCornerRadius">@dimen/card_view_card_corner_radius</item>
<item name="cardElevation">@dimen/card_view_card_elevation</item>
<item name="cardPreventCornerOverlap">false</item>
</style>
<style name="ChipStyle" parent="Base.Widget.MaterialComponents.Chip">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>

Loading…
Cancel
Save