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 package org.tasks.activities.attribution
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.widget.Toolbar import androidx.activity.viewModels
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import butterknife.ButterKnife import butterknife.ButterKnife
import com.google.common.collect.Multimaps import com.google.android.material.composethemeadapter.MdcTheme
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.tasks.R 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 org.tasks.injection.ThemedInjectingAppCompatActivity
import timber.log.Timber
import java.util.*
@AndroidEntryPoint @AndroidEntryPoint
class AttributionActivity : ThemedInjectingAppCompatActivity() { class AttributionActivity : ThemedInjectingAppCompatActivity() {
@BindView(R.id.toolbar) val viewModel: AttributionViewModel by viewModels()
lateinit var toolbar: Toolbar
@BindView(R.id.list)
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_attributions) val binding = ActivityAttributionsBinding.inflate(layoutInflater)
setContentView(binding.root)
ButterKnife.bind(this) ButterKnife.bind(this)
toolbar.setTitle(R.string.third_party_licenses) with(binding.toolbar.toolbar) {
toolbar.setNavigationIcon(R.drawable.ic_outline_arrow_back_24px) setTitle(R.string.third_party_licenses)
toolbar.setNavigationOnClickListener { finish() } setNavigationIcon(R.drawable.ic_outline_arrow_back_24px)
themeColor.apply(toolbar) setNavigationOnClickListener { finish() }
recyclerView.layoutManager = LinearLayoutManager(this) themeColor.apply(this)
} }
viewModel.attributions.observe(this) {
override fun onResume() { binding.compose.setContent {
super.onResume() MdcTheme {
ViewModelProvider(this) AttributionList(it)
.get(AttributionViewModel::class.java)
.observe(this, androidx.lifecycle.Observer { libraryAttributions: List<LibraryAttribution>? ->
updateAttributions(libraryAttributions!!)
})
} }
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 android.content.Context
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import io.reactivex.Single import dagger.hilt.android.lifecycle.HiltViewModel
import io.reactivex.android.schedulers.AndroidSchedulers import dagger.hilt.android.qualifiers.ApplicationContext
import io.reactivex.disposables.CompositeDisposable import kotlinx.coroutines.Dispatchers
import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.launch
import timber.log.Timber import kotlinx.coroutines.withContext
import java.io.InputStreamReader import java.io.InputStreamReader
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import javax.inject.Inject
class AttributionViewModel : ViewModel() { @HiltViewModel
private val attributions = MutableLiveData<List<LibraryAttribution>?>() class AttributionViewModel @Inject constructor(
private val disposables = CompositeDisposable() @ApplicationContext context: Context
private var loaded = false ): ViewModel() {
val attributions = MutableLiveData<Map<String, Map<String, List<LibraryAttribution>>>>()
fun observe(activity: AppCompatActivity, observer: Observer<List<LibraryAttribution>?>) { init {
attributions.observe(activity, observer) viewModelScope.launch {
load(activity) val licenses = withContext(Dispatchers.IO) {
context.assets.open("licenses.json")
} }
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 reader = InputStreamReader(licenses, StandardCharsets.UTF_8)
val list = GsonBuilder().create().fromJson(reader, AttributionList::class.java) 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 { internal class AttributionList {
@ -56,6 +46,5 @@ class AttributionViewModel : ViewModel() {
@get:Keep @get:Keep
var license: String? = null var license: String? = null
var libraryName: 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:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<include layout="@layout/toolbar"/> <include
android:id="@+id/toolbar"
layout="@layout/toolbar" />
<androidx.recyclerview.widget.RecyclerView <androidx.compose.ui.platform.ComposeView
android:id="@+id/list" android:id="@+id/compose"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent" />
android:background="?android:attr/windowBackground"
android:gravity="center"/>
</LinearLayout> </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="sku_details_row_text_size">14sp</dimen>
<dimen name="card_view_margin">8dp</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> <dimen name="header_gap">8dp</dimen>

@ -143,17 +143,6 @@
<item name="android:layout_height">24dp</item> <item name="android:layout_height">24dp</item>
</style> </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"> <style name="ChipStyle" parent="Base.Widget.MaterialComponents.Chip">
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item> <item name="android:layout_width">wrap_content</item>

Loading…
Cancel
Save