Refactoring of StatisticDetails & Graph

feature/top10
Aurelien Hubert 7 years ago
parent bbc2c7a2cc
commit 75675d4d75
  1. 7
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  2. 11
      app/src/main/java/net/pokeranalytics/android/ui/activity/StatisticDetailsActivity.kt
  3. 162
      app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt
  4. 127
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticDetailsFragment.kt
  5. 3
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt
  6. 14
      app/src/main/res/layout/activity_statistic_details.xml
  7. 25
      app/src/main/res/layout/fragment_evograph.xml
  8. 45
      app/src/main/res/layout/fragment_statistic_details.xml

@ -87,7 +87,8 @@ class Calculator {
fun computeStatsWithEvolutionByAggregationType( fun computeStatsWithEvolutionByAggregationType(
realm: Realm, realm: Realm,
group: ComputableGroup, group: ComputableGroup,
aggregationType: AggregationType aggregationType: AggregationType,
stats: List<Stat>? = null
): Report { ): Report {
val options = Options(evolutionValues = Options.EvolutionValues.STANDARD) val options = Options(evolutionValues = Options.EvolutionValues.STANDARD)
@ -95,6 +96,10 @@ class Calculator {
options.evolutionValues = Options.EvolutionValues.TIMED options.evolutionValues = Options.EvolutionValues.TIMED
} }
stats?.let {
options.displayedStats = stats
}
return when (aggregationType) { return when (aggregationType) {
AggregationType.SESSION, AggregationType.DURATION -> this.computeGroups(realm, listOf(group), options) AggregationType.SESSION, AggregationType.DURATION -> this.computeGroups(realm, listOf(group), options)
AggregationType.MONTH -> { AggregationType.MONTH -> {

@ -3,12 +3,14 @@ package net.pokeranalytics.android.ui.activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import com.google.android.libraries.places.internal.it
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ComputableGroup import net.pokeranalytics.android.calculus.ComputableGroup
import net.pokeranalytics.android.calculus.Report import net.pokeranalytics.android.calculus.Report
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.GraphParameters import net.pokeranalytics.android.ui.fragment.GraphParameters
import net.pokeranalytics.android.ui.fragment.StatisticDetailsFragment
class StatisticDetailsActivity : PokerAnalyticsActivity() { class StatisticDetailsActivity : PokerAnalyticsActivity() {
@ -47,6 +49,15 @@ class StatisticDetailsActivity : PokerAnalyticsActivity() {
*/ */
private fun initUI() { private fun initUI() {
val fragmentTransaction = supportFragmentManager.beginTransaction()
val statisticDetailsFragment = StatisticDetailsFragment()
fragmentTransaction.add(R.id.statisticDetailsContainer, statisticDetailsFragment)
fragmentTransaction.commit()
parameters?.let {
statisticDetailsFragment.setData(it.stat, it.computableGroup, it.report)
}
} }
} }

@ -4,33 +4,27 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible
import com.github.mikephil.charting.charts.BarChart import com.github.mikephil.charting.charts.BarChart
import com.github.mikephil.charting.charts.BarLineChartBase import com.github.mikephil.charting.charts.BarLineChartBase
import com.github.mikephil.charting.charts.LineChart import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.data.* import com.github.mikephil.charting.data.*
import com.github.mikephil.charting.highlight.Highlight import com.github.mikephil.charting.highlight.Highlight
import com.github.mikephil.charting.listener.OnChartValueSelectedListener import com.github.mikephil.charting.listener.OnChartValueSelectedListener
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import io.realm.Realm
import kotlinx.android.synthetic.main.fragment_evograph.* import kotlinx.android.synthetic.main.fragment_evograph.*
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.* import net.pokeranalytics.android.calculus.*
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.extensions.ChipGroupExtension
import net.pokeranalytics.android.ui.extensions.px
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.graph.PALineDataSet import net.pokeranalytics.android.ui.graph.PALineDataSet
import net.pokeranalytics.android.ui.graph.setStyle import net.pokeranalytics.android.ui.graph.setStyle
import net.pokeranalytics.android.ui.view.LegendView import net.pokeranalytics.android.ui.view.LegendView
import timber.log.Timber import timber.log.Timber
import java.util.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class GraphParameters(var stat: Stat, var computableGroup: ComputableGroup, var report: Report) {
class GraphParameters(var stat: Stat, var computableGroup: ComputableGroup, var report: Report) {
} }
class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, CoroutineScope { class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, CoroutineScope {
@ -49,15 +43,12 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
} }
private lateinit var parentActivity: PokerAnalyticsActivity private lateinit var parentActivity: PokerAnalyticsActivity
private lateinit var computableGroup: ComputableGroup
private lateinit var selectedReport: Report private lateinit var selectedReport: Report
private lateinit var legendView: LegendView private lateinit var legendView: LegendView
private lateinit var chartView: BarLineChartBase<*> private lateinit var chartView: BarLineChartBase<*>
private var stat: Stat = Stat.NET_RESULT private var stat: Stat = Stat.NET_RESULT
private var reports: MutableMap<AggregationType, Report> = hashMapOf() private var aggregationType: AggregationType = AggregationType.SESSION
private var aggregationTypes: List<AggregationType> = listOf()
private var displayAggregationChoices: Boolean = true
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = Dispatchers.Main get() = Dispatchers.Main
@ -70,26 +61,41 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initUI() initUI()
loadGraph()
} }
/** // OnChartValueSelectedListener
* Set data override fun onNothingSelected() {
*/ // nothing to do
fun setData(stat: Stat, group: ComputableGroup, report: Report, displayAggregationChoices: Boolean = true) { }
this.stat = report.options.displayedStats.first()
this.computableGroup = group
this.aggregationTypes = stat.aggregationTypes override fun onValueSelected(e: Entry?, h: Highlight?) {
this.reports[this.aggregationTypes.first()] = report e?.let { entry ->
this.selectedReport = report val statEntry = when (entry.data) {
this.displayAggregationChoices = displayAggregationChoices is ObjectIdentifier -> {
val identifier = entry.data as ObjectIdentifier
getRealm().where(identifier.clazz).equalTo("id", identifier.id).findAll().firstOrNull()
}
is StatEntry -> entry.data as StatEntry?
else -> null
}
statEntry?.let {
val formattedDate = it.entryTitle
val entryValue = it.formattedValue(this.stat, requireContext())
val totalStatValue = this.stat.format(entry.y.toDouble(), currency = null, context = requireContext())
this.legendView.setItemData(this.stat, formattedDate, entryValue, totalStatValue)
}
} }
}
/** /**
* Init UI * Init UI
*/ */
private fun initUI() { private fun initUI() {
Timber.d("initUI")
parentActivity = activity as PokerAnalyticsActivity parentActivity = activity as PokerAnalyticsActivity
parentActivity.title = stat.localizedTitle(requireContext()) parentActivity.title = stat.localizedTitle(requireContext())
@ -104,80 +110,20 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
this.chartView.setStyle(false, requireContext()) this.chartView.setStyle(false, requireContext())
this.chartContainer.addView(this.chartView) this.chartContainer.addView(this.chartView)
this.loadGraph(this.aggregationTypes.first(), this.selectedReport)
this.aggregationTypes.forEachIndexed { index, type ->
val chip = Chip(requireContext())
chip.id = index
chip.text = requireContext().getString(type.resId)
chip.chipStartPadding = 8f.px
chip.chipEndPadding = 8f.px
this.chipGroup.addView(chip)
}
this.chipGroup.isVisible = displayAggregationChoices
this.chipGroup.check(0)
this.chipGroup.setOnCheckedChangeListener(object : ChipGroupExtension.SingleSelectionOnCheckedListener() {
override fun onCheckedChanged(group: ChipGroup, checkedId: Int) {
super.onCheckedChanged(group, checkedId)
val aggregationType = aggregationTypes[checkedId]
reports[aggregationType]?.let {
loadGraph(aggregationType, it)
} ?: run {
launchStatComputation(aggregationType)
} }
} /**
}) * Load graph
*/
} private fun loadGraph() {
Timber.d("loadGraph")
private fun launchStatComputation(aggregationType: AggregationType) {
GlobalScope.launch(coroutineContext) {
var r: Report? = null
val test = GlobalScope.async {
val s = Date()
Timber.d(">>> start...")
val realm = Realm.getDefaultInstance()
val report =
Calculator.computeStatsWithEvolutionByAggregationType(realm, computableGroup, aggregationType)
reports[aggregationType] = report
r = report
realm.close()
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> ended in $duration seconds")
}
test.await()
if (!isDetached) {
r?.let {
loadGraph(aggregationType, it)
}
}
}
}
fun loadGraph(aggregationType: AggregationType, report: Report) {
val graphEntries = when (aggregationType) { val graphEntries = when (aggregationType) {
AggregationType.SESSION, AggregationType.DURATION -> report.results.firstOrNull()?.defaultStatEntries(stat) AggregationType.SESSION, AggregationType.DURATION -> selectedReport.results.firstOrNull()?.defaultStatEntries(stat)
AggregationType.MONTH, AggregationType.YEAR -> { AggregationType.MONTH, AggregationType.YEAR -> {
when (this.stat) { when (this.stat) {
Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> report.barEntries(this.stat) Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> selectedReport.barEntries(this.stat)
else -> report.lineEntries(this.stat) else -> selectedReport.lineEntries(this.stat)
} }
} }
} }
@ -216,32 +162,18 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
} }
// OnChartValueSelectedListener /**
* Set data
override fun onNothingSelected() { */
// nothing to do fun setData(report: Report, aggregationType: AggregationType) {
} Timber.d("setData")
override fun onValueSelected(e: Entry?, h: Highlight?) {
e?.let { entry ->
val statEntry = when (entry.data) {
is ObjectIdentifier -> {
val identifier = entry.data as ObjectIdentifier
getRealm().where(identifier.clazz).equalTo("id", identifier.id).findAll().firstOrNull()
}
is StatEntry -> entry.data as StatEntry?
else -> null
}
statEntry?.let {
val formattedDate = it.entryTitle this.selectedReport = report
val entryValue = it.formattedValue(this.stat, requireContext()) this.aggregationType = aggregationType
val totalStatValue = this.stat.format(entry.y.toDouble(), currency = null, context = requireContext()) this.stat = report.options.displayedStats.first()
this.legendView.setItemData(this.stat, formattedDate, entryValue, totalStatValue) if (isAdded && !isDetached) {
} loadGraph()
} }
} }

@ -6,13 +6,25 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.github.mikephil.charting.data.Entry import androidx.core.view.isVisible
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import io.realm.Realm
import kotlinx.android.synthetic.main.fragment_statistic_details.* import kotlinx.android.synthetic.main.fragment_statistic_details.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.*
import net.pokeranalytics.android.ui.activity.StatisticDetailsActivity import net.pokeranalytics.android.ui.activity.StatisticDetailsActivity.Companion.displayAggregationChoices
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.extensions.ChipGroupExtension
import net.pokeranalytics.android.ui.extensions.hideWithAnimation
import net.pokeranalytics.android.ui.extensions.px
import net.pokeranalytics.android.ui.extensions.showWithAnimation
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import timber.log.Timber
import java.util.*
class StatisticDetailsFragment : PokerAnalyticsFragment() { class StatisticDetailsFragment : PokerAnalyticsFragment() {
@ -24,9 +36,12 @@ class StatisticDetailsFragment : PokerAnalyticsFragment() {
} }
private lateinit var parentActivity: PokerAnalyticsActivity private lateinit var parentActivity: PokerAnalyticsActivity
private lateinit var computableGroup: ComputableGroup
private lateinit var graphFragment: GraphFragment
private lateinit var selectedReport: Report
private var reports: MutableMap<AggregationType, Report> = hashMapOf()
private var stat: Stat = Stat.NET_RESULT private var stat: Stat = Stat.NET_RESULT
private var entries: List<Entry> = ArrayList()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_statistic_details, container, false) return inflater.inflate(R.layout.fragment_statistic_details, container, false)
@ -41,6 +56,7 @@ class StatisticDetailsFragment : PokerAnalyticsFragment() {
* Init UI * Init UI
*/ */
private fun initUI() { private fun initUI() {
Timber.d("initUI")
parentActivity = activity as PokerAnalyticsActivity parentActivity = activity as PokerAnalyticsActivity
@ -51,29 +67,120 @@ class StatisticDetailsFragment : PokerAnalyticsFragment() {
parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true) parentActivity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
setHasOptionsMenu(true) setHasOptionsMenu(true)
toolbar.title = stat.localizedTitle(requireContext())
val fragmentManager = parentActivity.supportFragmentManager val fragmentManager = parentActivity.supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction() val fragmentTransaction = fragmentManager.beginTransaction()
val fragment = GraphFragment() graphFragment = GraphFragment()
fragmentTransaction.add(R.id.container, fragment) fragmentTransaction.add(R.id.graphContainer, graphFragment)
fragmentTransaction.commit() fragmentTransaction.commit()
stat.aggregationTypes.firstOrNull()?.let { aggregationType ->
reports[aggregationType]?.let { report ->
graphFragment.setData(report, aggregationType)
}
}
toolbar.title = stat.localizedTitle(requireContext())
val aggregationTypes = stat.aggregationTypes
aggregationTypes.forEachIndexed { index, type ->
val chip = Chip(requireContext())
chip.id = index
chip.text = requireContext().getString(type.resId)
chip.chipStartPadding = 8f.px
chip.chipEndPadding = 8f.px
this.chipGroup.addView(chip)
}
this.chipGroup.isVisible = displayAggregationChoices
this.chipGroup.check(0)
this.chipGroup.setOnCheckedChangeListener(object : ChipGroupExtension.SingleSelectionOnCheckedListener() {
override fun onCheckedChanged(group: ChipGroup, checkedId: Int) {
super.onCheckedChanged(group, checkedId)
val aggregationType = aggregationTypes[checkedId]
reports[aggregationType]?.let { report ->
graphFragment.setData(report, aggregationType)
} ?: run {
launchStatComputation(aggregationType)
}
}
})
/*
StatisticDetailsActivity.parameters?.let { StatisticDetailsActivity.parameters?.let {
fragment.setData(it.stat, it.computableGroup, it.report, StatisticDetailsActivity.displayAggregationChoices)
//TODO: Set in the function setData
//stat = it.stat
graphFragment.setData(report, aggregationType)
//launchStatComputation(aggregationType)
//graphFragment.setData(it.stat, it.computableGroup, it.report, StatisticDetailsActivity.displayAggregationChoices)
StatisticDetailsActivity.parameters = null StatisticDetailsActivity.parameters = null
} ?: run { } ?: run {
throw Exception("Missing graph parameters") throw Exception("Missing graph parameters")
} }
*/
} }
/**
* Launch stat computation
*/
private fun launchStatComputation(aggregationType: AggregationType) {
graphContainer.hideWithAnimation()
progressBar.showWithAnimation()
//TODO: Create loader here
Timber.d("launchStatComputation: $aggregationType")
GlobalScope.launch {
val s = Date()
Timber.d(">>> start...")
val realm = Realm.getDefaultInstance()
val requiredStats: List<Stat> = listOf(stat)
val report = Calculator.computeStatsWithEvolutionByAggregationType(realm, computableGroup, aggregationType, requiredStats)
reports[aggregationType] = report
realm.close()
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> ended in $duration seconds")
launch(Dispatchers.Main) {
graphFragment.setData(report, aggregationType)
progressBar.hideWithAnimation()
graphContainer.showWithAnimation()
}
}
}
/** /**
* Set data * Set data
*/ */
fun setData(stat: Stat, entries: List<Entry>) { fun setData(stat: Stat, computableGroup: ComputableGroup, report: Report) {
Timber.d("setData")
this.stat = stat this.stat = stat
this.entries = entries this.computableGroup = computableGroup
this.selectedReport = report
stat.aggregationTypes.firstOrNull()?.let {
reports[it] = report
}
} }
} }

@ -246,8 +246,9 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
val requiredStats: List<Stat> = listOf(stat)
val aggregationType = stat.aggregationTypes.first() val aggregationType = stat.aggregationTypes.first()
report = Calculator.computeStatsWithEvolutionByAggregationType(realm, computableGroup, aggregationType) report = Calculator.computeStatsWithEvolutionByAggregationType(realm, computableGroup, aggregationType, requiredStats)
realm.close() realm.close()

@ -1,15 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/statisticDetailsContainer">
android:orientation="vertical">
<fragment </FrameLayout>
android:id="@+id/statisticDetailsFragment"
android:name="net.pokeranalytics.android.ui.fragment.StatisticDetailsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_statistic_details" />
</LinearLayout>

@ -19,32 +19,9 @@
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@id/chips"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/legendContainer" />
<FrameLayout
android:id="@+id/chips"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
tools:layout_height="60dp"> app:layout_constraintTop_toBottomOf="@+id/legendContainer" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal"
app:chipSpacingHorizontal="8dp"
app:singleSelection="true"
tools:layout_height="60dp" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

@ -14,13 +14,54 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:title="@string/app_name" /> tools:title="@string/app_name" />
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:alpha="0"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/chips"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
tools:alpha="1"
tools:visibility="visible" />
<FrameLayout <FrameLayout
android:id="@+id/container" android:id="@+id/graphContainer"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toTopOf="@+id/chips"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" /> app:layout_constraintTop_toBottomOf="@+id/toolbar" />
<FrameLayout
android:id="@+id/chips"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:layout_height="60dp">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal"
app:chipSpacingHorizontal="8dp"
app:singleSelection="true"
tools:layout_height="60dp" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

Loading…
Cancel
Save