Refactor HistoryFragment and add transaction management

dev
Aurelien Hubert 7 years ago
parent e80ef58493
commit d77317cdde
  1. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt
  2. 4
      app/src/main/java/net/pokeranalytics/android/ui/adapter/ComparisonChartPagerAdapter.kt
  3. 10
      app/src/main/java/net/pokeranalytics/android/ui/adapter/FeedSessionRowRepresentableAdapter.kt
  4. 167
      app/src/main/java/net/pokeranalytics/android/ui/adapter/FeedTransactionRowRepresentableAdapter.kt
  5. 6
      app/src/main/java/net/pokeranalytics/android/ui/adapter/HomePagerAdapter.kt
  6. 56
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt
  7. 4
      app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt
  8. 69
      app/src/main/java/net/pokeranalytics/android/ui/view/SessionRowView.kt
  9. 7
      app/src/main/res/layout/fragment_feed.xml
  10. 0
      app/src/main/res/layout/row_feed_session.xml

@ -11,6 +11,7 @@ import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.DatedValue
import net.pokeranalytics.android.model.interfaces.Manageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.model.interfaces.TimeFilterable
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
@ -19,9 +20,10 @@ import java.util.*
import kotlin.collections.ArrayList
open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable, Filterable, DatedValue {
open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable, TimeFilterable, Filterable, DatedValue {
companion object {
val rowRepresentation: List<RowRepresentable> by lazy {
val rows = ArrayList<RowRepresentable>()
rows.addAll(TransactionRow.values())
@ -55,6 +57,12 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
// A user comment
var comment: String = ""
// Timed interface
override var dayOfWeek: Int? = null
override var month: Int? = null
override var year: Int? = null
override var dayOfMonth: Int? = null
@Ignore
override val viewType: Int = RowViewType.ROW_TRANSACTION.ordinal

@ -8,7 +8,7 @@ import androidx.fragment.app.FragmentStatePagerAdapter
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.fragment.CalendarFragment
import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.fragment.HistoryFragment
import net.pokeranalytics.android.ui.fragment.FeedFragment
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import java.lang.ref.WeakReference
@ -24,7 +24,7 @@ class ComparisonChartPagerAdapter(val context: Context, fragmentManager: Fragmen
0 -> GraphFragment()
1 -> GraphFragment()
2 -> CalendarFragment.newInstance()
else -> HistoryFragment.newInstance()
else -> FeedFragment.newInstance()
}
}

@ -7,7 +7,7 @@ import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.RecyclerView
import io.realm.RealmResults
import kotlinx.android.synthetic.main.row_history_session.view.*
import kotlinx.android.synthetic.main.row_feed_session.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.view.BindableHolder
@ -24,7 +24,7 @@ import kotlin.collections.HashMap
* @param dataSource the datasource providing rows
* @param delegate the delegate, notified of UI actions
*/
class HistorySessionRowRepresentableAdapter(
class FeedSessionRowRepresentableAdapter(
var delegate: RowRepresentableDelegate? = null,
var realmResults: RealmResults<Session>,
var pendingRealmResults: RealmResults<Session>,
@ -43,7 +43,7 @@ class HistorySessionRowRepresentableAdapter(
* Display a session view
*/
inner class RowSessionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
fun bind(position: Int, row: Session?, adapter: HistorySessionRowRepresentableAdapter) {
fun bind(position: Int, row: Session?, adapter: FeedSessionRowRepresentableAdapter) {
itemView.sessionRow.setData(row as Session)
val listener = View.OnClickListener {
@ -57,7 +57,7 @@ class HistorySessionRowRepresentableAdapter(
* Display a session view
*/
inner class HeaderTitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
fun bind(position: Int, title: String, adapter: HistorySessionRowRepresentableAdapter) {
fun bind(position: Int, title: String, adapter: FeedSessionRowRepresentableAdapter) {
// Title
itemView.findViewById<AppCompatTextView>(R.id.title)?.let {
it.text = title
@ -67,7 +67,7 @@ class HistorySessionRowRepresentableAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (viewType == RowViewType.ROW_SESSION.ordinal) {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_history_session, parent, false)
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_feed_session, parent, false)
return RowSessionViewHolder(layout)
} else {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_header_title, parent, false)

@ -0,0 +1,167 @@
package net.pokeranalytics.android.ui.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.RecyclerView
import io.realm.RealmResults
import kotlinx.android.synthetic.main.row_transaction.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.ui.view.BindableHolder
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.extensions.getMonthAndYear
import timber.log.Timber
import java.util.*
import kotlin.collections.HashMap
/**
* An adapter capable of displaying a list of RowRepresentables
* @param dataSource the datasource providing rows
* @param delegate the delegate, notified of UI actions
*/
class FeedTransactionRowRepresentableAdapter(
var delegate: RowRepresentableDelegate? = null,
var realmTransactions: RealmResults<Transaction>,
var distinctTransactionsHeaders: RealmResults<Transaction>
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var headersPositions = HashMap<Int, Date?>()
private lateinit var sortedHeaders: SortedMap<Int, Date?>
init {
refreshData()
}
/**
* Display a transaction view
*/
inner class RowTransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
fun bind(position: Int, row: Transaction?, adapter: FeedTransactionRowRepresentableAdapter) {
itemView.transactionRow.setData(row as Transaction)
val listener = View.OnClickListener {
adapter.delegate?.onRowSelected(position, row)
}
itemView.transactionRow.setOnClickListener(listener)
}
}
/**
* Display a header
*/
inner class HeaderTitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), BindableHolder {
fun bind(position: Int, title: String, adapter: FeedTransactionRowRepresentableAdapter) {
// Title
itemView.findViewById<AppCompatTextView>(R.id.title)?.let {
it.text = title
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (viewType == RowViewType.ROW_TRANSACTION.ordinal) {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_transaction, parent, false)
return RowTransactionViewHolder(layout)
} else {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.row_header_title, parent, false)
return HeaderTitleViewHolder(layout)
}
}
override fun getItemViewType(position: Int): Int {
if (sortedHeaders.containsKey(position)) {
return RowViewType.HEADER_TITLE.ordinal
} else {
return RowViewType.ROW_TRANSACTION.ordinal
}
}
override fun getItemCount(): Int {
return realmTransactions.size + distinctTransactionsHeaders.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is RowTransactionViewHolder) {
holder.bind(position, getTransactionForPosition(position), this)
} else if (holder is HeaderTitleViewHolder) {
holder.bind(position, getHeaderForPosition(holder.itemView.context, position), this)
}
}
/**
* Return the header
*/
private fun getHeaderForPosition(context: Context, position: Int): String {
if (sortedHeaders.containsKey(position)) {
val realmHeaderPosition = sortedHeaders.keys.indexOf(position)
return distinctTransactionsHeaders[realmHeaderPosition]?.date?.getMonthAndYear() ?: ""
}
return NULL_TEXT
}
/**
* Get real index
*/
private fun getTransactionForPosition(position: Int): Transaction? {
// Row position
var headersBefore = 0
for (key in sortedHeaders.keys) {
if (position > key) {
headersBefore++
} else {
break
}
}
return realmTransactions[position - headersBefore]
}
/**
* Refresh headers positions
*/
fun refreshData() {
headersPositions.clear()
val start = System.currentTimeMillis()
var previousYear = Int.MAX_VALUE
var previousMonth = Int.MAX_VALUE
val calendar = Calendar.getInstance()
// Add headers if the date doesn't exist yet
for ((index, transaction) in realmTransactions.withIndex()) {
calendar.time = transaction.date
if (checkHeaderCondition(calendar, previousYear, previousMonth)) {
headersPositions[index + headersPositions.size] = transaction.date
previousYear = calendar.get(Calendar.YEAR)
previousMonth = calendar.get(Calendar.MONTH)
}
}
sortedHeaders = headersPositions.toSortedMap()
Timber.d("Create viewTypesPositions in: ${System.currentTimeMillis() - start}ms")
}
/**
* Check if we need to add a header
* Can be change to manage different condition
*/
private fun checkHeaderCondition(currentCalendar: Calendar, previousYear: Int, previousMonth: Int): Boolean {
return currentCalendar.get(Calendar.YEAR) == previousYear && currentCalendar.get(Calendar.MONTH) < previousMonth || (currentCalendar.get(Calendar.YEAR) < previousYear)
}
}

@ -17,12 +17,12 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda
override fun getItem(position: Int): PokerAnalyticsFragment {
return when (position) {
0 -> HistoryFragment.newInstance()
0 -> FeedFragment.newInstance()
1 -> StatisticsFragment.newInstance()
2 -> CalendarFragment.newInstance()
3 -> ReportsFragment.newInstance()
4 -> MoreFragment.newInstance()
else -> HistoryFragment.newInstance()
else -> FeedFragment.newInstance()
}
}
@ -43,7 +43,7 @@ class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAda
override fun getItemPosition(obj: Any): Int {
return when (obj) {
HistoryFragment::class.java -> 0
FeedFragment::class.java -> 0
StatisticsFragment::class.java -> 1
CalendarFragment::class.java -> 2
ReportsFragment::class.java -> 3

@ -13,16 +13,17 @@ import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import io.realm.RealmResults
import io.realm.Sort
import io.realm.kotlin.where
import kotlinx.android.synthetic.main.fragment_history.*
import kotlinx.android.synthetic.main.fragment_feed.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Editable
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.ui.activity.EditableDataActivity
import net.pokeranalytics.android.ui.activity.NewDataMenuActivity
import net.pokeranalytics.android.ui.activity.SessionActivity
import net.pokeranalytics.android.ui.adapter.HistorySessionRowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.LiveRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.FeedSessionRowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.FeedTransactionRowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.view.RowRepresentable
@ -35,29 +36,30 @@ import java.util.*
class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate {
class FeedFragment : PokerAnalyticsFragment(), RowRepresentableDelegate {
companion object {
const val REQUEST_CODE_MENU = 100
fun newInstance(): HistoryFragment {
val fragment = HistoryFragment()
fun newInstance(): FeedFragment {
val fragment = FeedFragment()
val bundle = Bundle()
fragment.arguments = bundle
return fragment
}
}
private lateinit var historyAdapter: HistorySessionRowRepresentableAdapter
private lateinit var feedSessionAdapter: FeedSessionRowRepresentableAdapter
private lateinit var feedTransactionAdapter: FeedTransactionRowRepresentableAdapter
private lateinit var realmSessions: RealmResults<Session>
private lateinit var realmTransactions: RealmResults<Transaction>
private lateinit var betaLimitDate: Date
private val rows: ArrayList<RowRepresentable> = ArrayList()
private var newSessionCreated: Boolean = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_history, container, false)
return inflater.inflate(R.layout.fragment_feed, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -82,22 +84,6 @@ class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource
realmSessions.removeAllChangeListeners()
}
override fun rowRepresentableForPosition(position: Int): RowRepresentable? {
return this.rows[position]
}
override fun numberOfRows(): Int {
return this.rows.size
}
override fun viewTypeForPosition(position: Int): Int {
return rows[position].viewType
}
override fun indexForRow(row: RowRepresentable): Int {
return this.rows.indexOf(row)
}
override fun onRowSelected(position: Int, row: RowRepresentable, fromAction: Boolean) {
SessionActivity.newInstance(requireContext(), sessionId = (row as Editable).id)
}
@ -125,6 +111,15 @@ class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource
startActivityForResult(intent, REQUEST_CODE_MENU, options.toBundle())
}
}
filterSessions.setOnClickListener {
recyclerView.adapter = this.feedSessionAdapter
}
filterTransactions.setOnClickListener {
recyclerView.adapter = this.feedTransactionAdapter
}
}
/**
@ -137,21 +132,24 @@ class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource
this.realmSessions = getRealm().where<Session>().findAll().sort("startDate", Sort.DESCENDING)
this.realmSessions.addChangeListener { _, _ ->
this.historyAdapter.refreshData()
this.historyAdapter.notifyDataSetChanged()
this.feedSessionAdapter.refreshData()
this.feedSessionAdapter.notifyDataSetChanged()
}
val startedSessions = getRealm().where<Session>().isNotNull("year").isNotNull("month").findAll().sort("startDate", Sort.DESCENDING)
val pendingSessions = getRealm().where<Session>().isNull("year").isNull("month").findAll().sort("startDate", Sort.DESCENDING)
val distinctDateSessions = getRealm().where<Session>().distinct("year", "month").findAll().sort("startDate", Sort.DESCENDING)
this.feedSessionAdapter = FeedSessionRowRepresentableAdapter(this, realmSessions, pendingSessions, distinctDateSessions)
this.historyAdapter = HistorySessionRowRepresentableAdapter(this, startedSessions, pendingSessions, distinctDateSessions)
this.realmTransactions = getRealm().where<Transaction>().findAll().sort("date", Sort.DESCENDING)
val distinctDateTransactions = getRealm().where<Transaction>().distinct("year", "month").findAll().sort("date", Sort.DESCENDING)
this.feedTransactionAdapter = FeedTransactionRowRepresentableAdapter(this, realmTransactions, distinctDateTransactions)
val viewManager = SmoothScrollLinearLayoutManager(requireContext())
recyclerView.apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = historyAdapter
adapter = feedSessionAdapter
}
}

@ -17,7 +17,7 @@ import com.github.mikephil.charting.data.BarData
import com.github.mikephil.charting.data.BarDataSet
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import kotlinx.android.synthetic.main.row_history_session.view.*
import kotlinx.android.synthetic.main.row_feed_session.view.*
import kotlinx.android.synthetic.main.row_transaction.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ComputedStat
@ -71,7 +71,7 @@ enum class RowViewType(private var layoutRes: Int) {
LOADER(R.layout.row_loader),
// Custom row
ROW_SESSION(R.layout.row_history_session),
ROW_SESSION(R.layout.row_feed_session),
ROW_TRANSACTION(R.layout.row_transaction),
ROW_BUTTON(R.layout.row_button),
ROW_FOLLOW_US(R.layout.row_follow_us),

@ -26,7 +26,7 @@ import net.pokeranalytics.android.util.extensions.toCurrency
*/
class SessionRowView : FrameLayout {
private lateinit var rowHistorySession: ConstraintLayout
private lateinit var rowSession: ConstraintLayout
/**
* Constructors
@ -48,9 +48,9 @@ class SessionRowView : FrameLayout {
*/
private fun init() {
val layoutInflater = LayoutInflater.from(context)
rowHistorySession = layoutInflater.inflate(R.layout.row_session_view, this, false) as ConstraintLayout
rowSession = layoutInflater.inflate(R.layout.row_session_view, this, false) as ConstraintLayout
val layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)
addView(rowHistorySession, layoutParams)
addView(rowSession, layoutParams)
}
/**
@ -61,8 +61,8 @@ class SessionRowView : FrameLayout {
val date = session.startDate ?: session.creationDate
// Date
rowHistorySession.dateDay.text = date.getShortDayName()
rowHistorySession.dateNumber.text = date.getDayNumber()
rowSession.dateDay.text = date.getShortDayName()
rowSession.dateNumber.text = date.getDayNumber()
// Title / Game type
@ -99,60 +99,55 @@ class SessionRowView : FrameLayout {
}
val title = parameters.joinToString(separator = " ")
rowHistorySession.sessionTitle.text = title
rowSession.sessionTitle.text = title
// Duration
// rowHistorySession.sessionInfoDurationIcon.isVisible = session.timeFrame != null
// rowHistorySession.sessionInfoDurationValue.isVisible = session.timeFrame != null
// session.timeFrame?.let {
// rowHistorySession.sessionInfoDurationValue.text = session.getFormattedDuration()
// }
rowHistorySession.sessionInfoDurationValue.text = session.getFormattedDuration()
rowSession.sessionInfoDurationValue.text = session.getFormattedDuration()
// Location
rowHistorySession.sessionInfoLocationIcon.isVisible = session.location != null
rowHistorySession.sessionInfoLocationValue.isVisible = session.location != null
rowSession.sessionInfoLocationIcon.isVisible = session.location != null
rowSession.sessionInfoLocationValue.isVisible = session.location != null
session.location?.let {
rowHistorySession.sessionInfoLocationValue.text = it.name
rowSession.sessionInfoLocationValue.text = it.name
}
// Table size
rowHistorySession.sessionInfoTableIcon.isVisible = session.tableSize != null
rowHistorySession.sessionInfoTableValue.isVisible = session.tableSize != null
rowSession.sessionInfoTableIcon.isVisible = session.tableSize != null
rowSession.sessionInfoTableValue.isVisible = session.tableSize != null
session.tableSize?.let {
rowHistorySession.sessionInfoTableValue.text = TableSize(it).localizedTitle(context)
rowSession.sessionInfoTableValue.text = TableSize(it).localizedTitle(context)
}
val state = session.getState()
rowHistorySession.sessionInfoDurationIcon.isVisible = state.hasStarted
rowHistorySession.sessionInfoDurationValue.isVisible = state.hasStarted
rowSession.sessionInfoDurationIcon.isVisible = state.hasStarted
rowSession.sessionInfoDurationValue.isVisible = state.hasStarted
// State
if (state == SessionState.STARTED) {
rowHistorySession.gameResult.isVisible = false
rowHistorySession.infoIcon.isVisible = true
rowHistorySession.infoIcon.setImageResource(R.drawable.chip)
rowHistorySession.infoTitle.isVisible = true
rowHistorySession.infoTitle.text = context.getString(R.string.running_session_state)
rowSession.gameResult.isVisible = false
rowSession.infoIcon.isVisible = true
rowSession.infoIcon.setImageResource(R.drawable.chip)
rowSession.infoTitle.isVisible = true
rowSession.infoTitle.text = context.getString(R.string.running_session_state)
} else if (state == SessionState.PLANNED) {
rowHistorySession.gameResult.isVisible = false
rowHistorySession.infoIcon.isVisible = true
rowHistorySession.infoIcon.setImageResource(R.drawable.ic_planned)
rowHistorySession.infoTitle.isVisible = true
rowHistorySession.infoTitle.text = session.startDate!!.shortTime()
rowSession.gameResult.isVisible = false
rowSession.infoIcon.isVisible = true
rowSession.infoIcon.setImageResource(R.drawable.ic_planned)
rowSession.infoTitle.isVisible = true
rowSession.infoTitle.text = session.startDate!!.shortTime()
} else if (state == SessionState.PENDING) {
rowHistorySession.gameResult.isVisible = false
rowHistorySession.infoIcon.isVisible = false
rowHistorySession.infoTitle.isVisible = false
rowSession.gameResult.isVisible = false
rowSession.infoIcon.isVisible = false
rowSession.infoTitle.isVisible = false
} else {
rowHistorySession.gameResult.isVisible = true
rowHistorySession.infoIcon.isVisible = false
rowHistorySession.infoTitle.isVisible = false
rowSession.gameResult.isVisible = true
rowSession.infoIcon.isVisible = false
rowSession.infoTitle.isVisible = false
val result = session.result?.net ?: 0.0
val formattedStat = ComputedStat(Stat.NET_RESULT, result, currency = session.currency).format()
rowHistorySession.gameResult.setTextFormat(formattedStat, context)
rowSession.gameResult.setTextFormat(formattedStat, context)
}
}

@ -30,21 +30,24 @@
app:chipSpacing="8dp"
app:singleSelection="true">
<!--
<com.google.android.material.chip.Chip
android:id="@+id/filterAll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/all" />
-->
<com.google.android.material.chip.Chip
android:id="@+id/filterSessions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/sessions" />
<com.google.android.material.chip.Chip
android:id="@+id/filterOperations"
android:id="@+id/filterTransactions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/operations" />
@ -63,7 +66,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appBar"
tools:listitem="@layout/row_history_session" />
tools:listitem="@layout/row_feed_session" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/noSessionFound"
Loading…
Cancel
Save