Merge branch 'dev' of gitlab.com:stax-river/poker-analytics into dev

dev
Laurent 7 years ago
commit 595bea05a6
  1. 24
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Manageable.kt
  2. 22
      app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt
  3. 9
      app/src/main/java/net/pokeranalytics/android/model/realm/Transaction.kt
  4. 17
      app/src/main/java/net/pokeranalytics/android/model/realm/TransactionType.kt
  5. 2
      app/src/main/java/net/pokeranalytics/android/ui/activity/BankrollActivity.kt
  6. 7
      app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollEditDataFragment.kt
  7. 90
      app/src/main/java/net/pokeranalytics/android/ui/fragment/BankrollFragment.kt
  8. 79
      app/src/main/java/net/pokeranalytics/android/ui/fragment/DataListFragment.kt
  9. 114
      app/src/main/java/net/pokeranalytics/android/ui/fragment/DeletableItemFragment.kt
  10. 3
      app/src/main/java/net/pokeranalytics/android/ui/fragment/EditableDataFragment.kt
  11. 24
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FeedFragment.kt
  12. 6
      app/src/main/java/net/pokeranalytics/android/ui/fragment/SessionFragment.kt
  13. 4
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/CustomizableRowRepresentable.kt
  14. 29
      app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/TransactionTypeRow.kt
  15. 14
      app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt
  16. 2
      app/src/main/res/layout/fragment_data_list.xml
  17. 49
      app/src/main/res/layout/fragment_feed.xml
  18. 3
      app/src/main/res/values-fr/strings.xml
  19. 3
      app/src/main/res/values/strings.xml

@ -12,12 +12,19 @@ enum class SaveValidityStatus {
DATA_INVALID; DATA_INVALID;
} }
enum class DeleteValidityStatus {
VALID,
INVALID,
SESSIONS_LINKED,
TRANSACTIONS_LINKED;
}
/** /**
* An interface to grouped object which are managed by the database * An interface to grouped object which are managed by the database
*/ */
interface Manageable : Savable, Deletable, Editable interface Manageable : Savable, Deletable, Editable
interface NameManageable: Manageable { interface NameManageable : Manageable {
var name: String var name: String
override fun isValidForSave(): Boolean { override fun isValidForSave(): Boolean {
@ -32,7 +39,7 @@ interface NameManageable: Manageable {
throw ModelException("${this::class.java} getFailedSaveMessage for $status not handled") throw ModelException("${this::class.java} getFailedSaveMessage for $status not handled")
} }
override fun getFailedDeleteMessage(): Int { override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
return R.string.relationship_error return R.string.relationship_error
} }
} }
@ -87,7 +94,7 @@ interface Savable : Identifiable {
/** /**
* A method to get the reason why the object can't be saved * A method to get the reason why the object can't be saved
*/ */
fun getFailedSaveMessage(status:SaveValidityStatus): Int fun getFailedSaveMessage(status: SaveValidityStatus): Int
} }
@ -101,8 +108,17 @@ interface Deletable : Identifiable {
*/ */
fun isValidForDelete(realm: Realm): Boolean fun isValidForDelete(realm: Realm): Boolean
fun getDeleteStatus(realm: Realm): DeleteValidityStatus {
if (!isValidForDelete(realm)) {
return DeleteValidityStatus.INVALID
}
return DeleteValidityStatus.VALID
}
/** /**
* A method to get the reason why the object can't be deleted * A method to get the reason why the object can't be deleted
*/ */
fun getFailedDeleteMessage(): Int fun getFailedDeleteMessage(status: DeleteValidityStatus): Int
} }

@ -7,6 +7,7 @@ import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where import io.realm.kotlin.where
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.model.interfaces.SaveValidityStatus import net.pokeranalytics.android.model.interfaces.SaveValidityStatus
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
@ -66,12 +67,25 @@ open class Bankroll : RealmObject(), NameManageable, RowRepresentable {
override fun isValidForDelete(realm: Realm): Boolean { override fun isValidForDelete(realm: Realm): Boolean {
return realm.where<Session>().equalTo("bankroll.id", id).findAll().isEmpty() return realm.where<Session>().equalTo("bankroll.id", id).findAll().isEmpty()
//TODO: Check transactions && realm.where<Transaction>().equalTo("bankroll.id", id).findAll().isEmpty()
//&& realm.where<Transaction>().equalTo("bankroll.id", id).findAll().isEmpty()
} }
override fun getFailedDeleteMessage(): Int { override fun getDeleteStatus(realm: Realm): DeleteValidityStatus {
return R.string.bankroll_relationship_error return if (!realm.where<Session>().equalTo("bankroll.id", id).findAll().isEmpty()) {
DeleteValidityStatus.SESSIONS_LINKED
} else if (!realm.where<Transaction>().equalTo("bankroll.id", id).findAll().isEmpty()) {
DeleteValidityStatus.TRANSACTIONS_LINKED
} else {
DeleteValidityStatus.VALID
}
}
override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
return when (status) {
DeleteValidityStatus.SESSIONS_LINKED -> R.string.bankroll_relationship_error
DeleteValidityStatus.TRANSACTIONS_LINKED -> R.string.bankroll_relationship_error_transactions
else -> super.getFailedDeleteMessage(status)
}
} }
override fun getFailedSaveMessage(status: SaveValidityStatus): Int { override fun getFailedSaveMessage(status: SaveValidityStatus): Int {

@ -10,10 +10,7 @@ import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.TextFormat import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.DatedValue import net.pokeranalytics.android.model.interfaces.*
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.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
@ -115,8 +112,8 @@ open class Transaction : RealmObject(), Manageable, StaticRowRepresentableDataSo
return true return true
} }
override fun getFailedDeleteMessage(): Int { override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. return R.string.relationship_error
} }
override fun getSaveValidityStatus(realm: Realm): SaveValidityStatus { override fun getSaveValidityStatus(realm: Realm): SaveValidityStatus {

@ -3,6 +3,8 @@ package net.pokeranalytics.android.model.realm
import io.realm.Realm import io.realm.Realm
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.DeleteValidityStatus
import net.pokeranalytics.android.model.interfaces.NameManageable import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
@ -69,6 +71,13 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
} }
} }
override fun boolForRow(row: RowRepresentable): Boolean {
return when (row) {
TransactionTypeRow.TRANSACTION_ADDITIVE -> this.additive
else -> super.boolForRow(row)
}
}
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? { override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return row.editingDescriptors(mapOf("defaultValue" to this.name)) return row.editingDescriptors(mapOf("defaultValue" to this.name))
} }
@ -76,15 +85,17 @@ open class TransactionType : RealmObject(), NameManageable, StaticRowRepresentab
override fun updateValue(value: Any?, row: RowRepresentable) { override fun updateValue(value: Any?, row: RowRepresentable) {
when (row) { when (row) {
SimpleRow.NAME -> this.name = value as String? ?: "" SimpleRow.NAME -> this.name = value as String? ?: ""
TransactionTypeRow.TRANSACTION_ADDITIVE -> this.additive = value as Boolean? ?: false
} }
} }
override fun isValidForDelete(realm: Realm): Boolean { override fun isValidForDelete(realm: Realm): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. return true
} }
override fun getFailedDeleteMessage(): Int { override fun getFailedDeleteMessage(status: DeleteValidityStatus): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates. return R.string.relationship_error
} }
} }

@ -69,10 +69,12 @@ class BankrollActivity : PokerAnalyticsActivity() {
indexes.addAll(changeSet.insertions.toList()) indexes.addAll(changeSet.insertions.toList())
indexes.addAll(changeSet.deletions.toList()) indexes.addAll(changeSet.deletions.toList())
indexes.forEach { index -> indexes.forEach { index ->
if (t.isNotEmpty()) {
t[index]?.bankroll?.let { br -> t[index]?.bankroll?.let { br ->
bankrolls.add(br) bankrolls.add(br)
} }
} }
}
this.computeBankrollReports(bankrolls) this.computeBankrollReports(bankrolls)
} }

@ -3,8 +3,6 @@ package net.pokeranalytics.android.ui.fragment
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.View import android.view.View
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.api.CurrencyConverterApi import net.pokeranalytics.android.api.CurrencyConverterApi
@ -56,11 +54,6 @@ class BankrollEditDataFragment : EditableDataFragment(), StaticRowRepresentableD
} ?: false } ?: false
} }
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
super.onCreateOptionsMenu(menu, inflater)
menu?.findItem(R.id.delete)?.isVisible = false
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

@ -6,7 +6,6 @@ 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.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.github.mikephil.charting.data.LineDataSet import com.github.mikephil.charting.data.LineDataSet
import io.realm.RealmObject import io.realm.RealmObject
@ -24,7 +23,7 @@ import net.pokeranalytics.android.calculus.bankroll.BankrollCalculator
import net.pokeranalytics.android.calculus.bankroll.BankrollReport import net.pokeranalytics.android.calculus.bankroll.BankrollReport
import net.pokeranalytics.android.calculus.bankroll.BankrollReportSetup import net.pokeranalytics.android.calculus.bankroll.BankrollReportSetup
import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Deletable import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.realm.Bankroll import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.ui.activity.BankrollDetailsActivity import net.pokeranalytics.android.ui.activity.BankrollDetailsActivity
import net.pokeranalytics.android.ui.activity.DataListActivity import net.pokeranalytics.android.ui.activity.DataListActivity
@ -34,7 +33,6 @@ import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable
@ -43,7 +41,7 @@ import timber.log.Timber
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class BankrollFragment : RealmFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate { class BankrollFragment : DeletableItemFragment(), StaticRowRepresentableDataSource, RowRepresentableDelegate {
companion object { companion object {
@ -66,9 +64,9 @@ class BankrollFragment : RealmFragment(), StaticRowRepresentableDataSource, RowR
private var rows: ArrayList<RowRepresentable> = ArrayList() private var rows: ArrayList<RowRepresentable> = ArrayList()
private var bankrollReportForRow: HashMap<RowRepresentable, BankrollReport> = HashMap() private var bankrollReportForRow: HashMap<RowRepresentable, BankrollReport> = HashMap()
private var deletedItem: RealmObject? = null
private var lastDeletedItemPosition: Int = 0
private var lastItemClickedPosition: Int = 0 private var lastItemClickedPosition: Int = 0
private var lastItemClickedId: String = ""
private var deletedRow: RowRepresentable? = null
// Life Cycle // Life Cycle
@ -91,7 +89,7 @@ class BankrollFragment : RealmFragment(), StaticRowRepresentableDataSource, RowR
if (needToDeleteItem) { if (needToDeleteItem) {
GlobalScope.launch(Dispatchers.Main) { GlobalScope.launch(Dispatchers.Main) {
delay(300) delay(300)
deleteItem(lastItemClickedPosition) deleteItem(bankrollAdapter, LiveData.BANKROLL.items(getRealm()), lastItemClickedId)
} }
} }
} else if (requestCode == REQUEST_CODE_CREATE && resultCode == Activity.RESULT_OK) { } else if (requestCode == REQUEST_CODE_CREATE && resultCode == Activity.RESULT_OK) {
@ -115,6 +113,7 @@ class BankrollFragment : RealmFragment(), StaticRowRepresentableDataSource, RowR
if (bankrollReportForRow.containsKey(row)) { if (bankrollReportForRow.containsKey(row)) {
bankrollReportForRow[row]?.let { bankrollReport -> bankrollReportForRow[row]?.let { bankrollReport ->
lastItemClickedPosition = position lastItemClickedPosition = position
lastItemClickedId = (row as Identifiable).id
BankrollDetailsActivity.newInstanceForResult(this, bankrollReport, REQUEST_CODE_DETAILS) BankrollDetailsActivity.newInstanceForResult(this, bankrollReport, REQUEST_CODE_DETAILS)
} }
} }
@ -136,6 +135,9 @@ class BankrollFragment : RealmFragment(), StaticRowRepresentableDataSource, RowR
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
// TODO: Improve that
// We are in the main thread...
val startDate = Date() val startDate = Date()
// Graph // Graph
@ -152,11 +154,13 @@ class BankrollFragment : RealmFragment(), StaticRowRepresentableDataSource, RowR
val bankrolls = LiveData.BANKROLL.items(getRealm()) as RealmResults<Bankroll> val bankrolls = LiveData.BANKROLL.items(getRealm()) as RealmResults<Bankroll>
bankrolls.forEach { bankrolls.forEach { bankroll ->
val bankrollReportSetup = BankrollReportSetup(it) val bankrollReportSetup = BankrollReportSetup(bankroll)
val bankrollReport = BankrollCalculator.computeReport(getRealm(), bankrollReportSetup) val bankrollReport = BankrollCalculator.computeReport(getRealm(), bankrollReportSetup)
val computedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.total) val computedStat = ComputedStat(Stat.NET_RESULT, bankrollReport.total)
val row = CustomizableRowRepresentable(RowViewType.TITLE_VALUE_ARROW, title = it.name, computedStat = computedStat, isSelectable = true) val row =
CustomizableRowRepresentable(RowViewType.TITLE_VALUE_ARROW, title = bankroll.name, computedStat = computedStat, isSelectable = true)
row.id = bankroll.id
rows.add(row) rows.add(row)
bankrollReportForRow[row] = bankrollReport bankrollReportForRow[row] = bankrollReport
@ -198,64 +202,26 @@ class BankrollFragment : RealmFragment(), StaticRowRepresentableDataSource, RowR
} }
} }
override fun updateUIAfterDeletion(itemPosition: Int) {
/** lastItemClickedPosition = rows.indexOfFirst { if (it is Identifiable) it.id == lastItemClickedId else false }
* Delete item deletedRow = rows.find { if (it is Identifiable) it.id == lastItemClickedId else false }
*/ rows.removeAt(lastItemClickedPosition)
private fun deleteItem(position: Int) { bankrollAdapter.notifyItemRemoved(lastItemClickedPosition)
//TODO: Get bankroll from bankrollReport and delete it
if (isDetached || activity == null) {
return
} }
// Save the delete position & create a copy of the object override fun updateUIAfterUndoDeletion(newItem: RealmObject) {
val bankrollReport = bankrollReportForRow[rowRepresentableForPosition(position)]
val mRecentlyDeletedItem = bankrollReport?.setup?.bankroll
lastDeletedItemPosition = position
if (mRecentlyDeletedItem is Bankroll) { // TODO: Improve that
// We are recreating a Bankroll report because the last one if invalid => the bankroll of the setup has been deleted
// Check if the object is valid for the deletion
if ((mRecentlyDeletedItem as Deletable).isValidForDelete(this.getRealm())) {
deletedItem = getRealm().copyFromRealm(mRecentlyDeletedItem)
getRealm().executeTransaction {
mRecentlyDeletedItem.deleteFromRealm()
}
//bankrollAdapter.notifyItemRemoved(position)
//showUndoSnackBar()
//TODO: Refresh bankrolls
initData()
} else { deletedRow?.let { row ->
bankrollAdapter.notifyItemChanged(position) val bankrollReportSetup = BankrollReportSetup(newItem as Bankroll)
val builder = AlertDialog.Builder(requireContext()) val bankrollReport = BankrollCalculator.computeReport(getRealm(), bankrollReportSetup)
.setMessage((mRecentlyDeletedItem as Deletable).getFailedDeleteMessage()) bankrollReportForRow[row] = bankrollReport
.setNegativeButton(R.string.ok, null)
builder.show()
}
}
}
/** rows.add(lastItemClickedPosition, row)
* Show undo snack bar bankrollAdapter.notifyItemInserted(lastItemClickedPosition)
*/
private fun showUndoSnackBar() {
/*
val message = String.format(getString(R.string.data_deleted), getString(R.string.bankroll))
val snackBar = Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_INDEFINITE)
snackBar.setAction(R.string.cancel) {
getRealm().executeTransaction { realm ->
deletedItem?.let {
realm.copyToRealmOrUpdate(it)
bankrollAdapter.notifyItemInserted(lastDeletedItemPosition)
}
} }
} }
snackBar.show()
*/
}
} }

@ -6,11 +6,8 @@ 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.appcompat.app.AlertDialog
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import kotlinx.android.synthetic.main.fragment_data_list.* import kotlinx.android.synthetic.main.fragment_data_list.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -19,7 +16,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.LiveData import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.Identifiable import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.ui.activity.DataListActivity import net.pokeranalytics.android.ui.activity.DataListActivity
@ -29,12 +25,11 @@ import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.adapter.LiveRowRepresentableDataSource import net.pokeranalytics.android.ui.adapter.LiveRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback import net.pokeranalytics.android.ui.helpers.SwipeToDeleteCallback
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate { class DataListFragment : DeletableItemFragment(), LiveRowRepresentableDataSource, RowRepresentableDelegate {
companion object { companion object {
const val REQUEST_CODE_DETAILS = 1000 const val REQUEST_CODE_DETAILS = 1000
@ -43,10 +38,7 @@ class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRep
private lateinit var dataType: LiveData private lateinit var dataType: LiveData
private lateinit var items: RealmResults<*> private lateinit var items: RealmResults<*>
private lateinit var dataListAdapter: RowRepresentableAdapter private lateinit var dataListAdapter: RowRepresentableAdapter
private var lastItemClickedId: String = ""
private var deletedItem: RealmObject? = null
private var lastDeletedItemPosition: Int = 0
private var lastItemClickedPosition: Int = 0
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
@ -61,11 +53,12 @@ class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRep
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_DETAILS && resultCode == Activity.RESULT_OK) { if (requestCode == REQUEST_CODE_DETAILS && resultCode == Activity.RESULT_OK) {
val needToDeleteItem = data?.getBooleanExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName, false) ?: false val needToDeleteItem =
data?.getBooleanExtra(DataListActivity.IntentKey.ITEM_DELETED.keyName, false) ?: false
if (needToDeleteItem) { if (needToDeleteItem) {
GlobalScope.launch(Dispatchers.Main) { GlobalScope.launch(Dispatchers.Main) {
delay(300) delay(300)
deleteItem(lastItemClickedPosition) deleteItem(dataListAdapter, items, lastItemClickedId)
} }
} }
} }
@ -101,13 +94,8 @@ class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRep
activity?.setResult(Activity.RESULT_OK, intent) activity?.setResult(Activity.RESULT_OK, intent)
activity?.finish() activity?.finish()
} else { } else {
lastItemClickedPosition = position lastItemClickedId = (row as Identifiable).id
EditableDataActivity.newInstanceForResult( EditableDataActivity.newInstanceForResult(this, it.ordinal, lastItemClickedId, REQUEST_CODE_DETAILS)
this,
it.ordinal,
(row as Identifiable).id,
REQUEST_CODE_DETAILS
)
} }
} }
} }
@ -130,7 +118,8 @@ class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRep
dataListAdapter = RowRepresentableAdapter(this, this) dataListAdapter = RowRepresentableAdapter(this, this)
val swipeToDelete = SwipeToDeleteCallback(dataListAdapter) { position -> val swipeToDelete = SwipeToDeleteCallback(dataListAdapter) { position ->
deleteItem(position) val itemId = (this.items[position] as Identifiable).id
deleteItem(dataListAdapter, items, itemId)
} }
val itemTouchHelper = ItemTouchHelper(swipeToDelete) val itemTouchHelper = ItemTouchHelper(swipeToDelete)
@ -153,56 +142,6 @@ class DataListFragment : RealmFragment(), LiveRowRepresentableDataSource, RowRep
} }
} }
/**
* Delete item
*/
private fun deleteItem(position: Int) {
if (isDetached || activity == null) {
return
}
// Save the delete position & create a copy of the object
val mRecentlyDeletedItem = rowRepresentableForPosition(position)
lastDeletedItemPosition = position
if (mRecentlyDeletedItem is RealmObject) {
// Check if the object is valid for the deletion
if ((mRecentlyDeletedItem as Deletable).isValidForDelete(this.getRealm())) {
deletedItem = getRealm().copyFromRealm(mRecentlyDeletedItem)
getRealm().executeTransaction {
mRecentlyDeletedItem.deleteFromRealm()
}
dataListAdapter.notifyItemRemoved(position)
showUndoSnackBar()
} else {
dataListAdapter.notifyItemChanged(position)
val builder = AlertDialog.Builder(requireContext())
.setMessage((mRecentlyDeletedItem as Deletable).getFailedDeleteMessage())
.setNegativeButton(R.string.ok, null)
builder.show()
}
}
}
/**
* Show undo snack bar
*/
private fun showUndoSnackBar() {
val message = String.format(getString(R.string.data_deleted), this.dataType.localizedTitle(requireContext()))
val snackBar = Snackbar.make(constraintLayout, message, Snackbar.LENGTH_INDEFINITE)
snackBar.setAction(R.string.cancel) {
getRealm().executeTransaction { realm ->
deletedItem?.let {
realm.copyToRealmOrUpdate(it)
dataListAdapter.notifyItemInserted(lastDeletedItemPosition)
}
}
}
snackBar.show()
}
/** /**
* Set fragment data * Set fragment data
*/ */

@ -0,0 +1,114 @@
package net.pokeranalytics.android.ui.fragment
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.snackbar.Snackbar
import io.realm.RealmObject
import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.interfaces.Deletable
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.fragment.components.RealmFragment
/**
* Deletable Item Fragment
* Don't forget to add a CoordinatorLayout at the top of your XML if you want to display correctly the snack bar
*/
open class DeletableItemFragment : RealmFragment() {
private var deletedItem: RealmObject? = null
private var lastDeletedItemPosition: Int = 0
private var dataListAdapter: RowRepresentableAdapter? = null
private var coordinatorLayout: CoordinatorLayout? = null
private var snackBar: Snackbar? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
this.coordinatorLayout = view.findViewById(R.id.coordinatorLayout)
}
override fun onPause() {
super.onPause()
snackBar?.dismiss()
}
/**
* Delete item
* [dataListAdapter]: Adapter to update
* [items]: List of items which contains the element to delete
* [itemId]: Id of the item to delete
* [container]: View to display the Snackbar
*/
fun deleteItem(dataListAdapter: RowRepresentableAdapter, items: List<*>, itemId: String) {
if (isDetached || activity == null) {
return
}
this.dataListAdapter = dataListAdapter
// Save the delete position & create a copy of the object
val itemPosition = items.indexOfFirst { (it as Identifiable).id == itemId }
val itemToDelete = items.find { (it as Identifiable).id == itemId }
if (itemToDelete is RealmObject && itemPosition != -1) {
val deletableItem = (itemToDelete as Deletable)
// Check if the object is valid for the deletion
if (deletableItem.isValidForDelete(this.getRealm())) {
deletedItem = getRealm().copyFromRealm(itemToDelete)
lastDeletedItemPosition = itemPosition
getRealm().executeTransaction {
itemToDelete.deleteFromRealm()
}
updateUIAfterDeletion(itemPosition)
showUndoSnackBar()
} else {
dataListAdapter.notifyItemChanged(itemPosition)
val status = deletableItem.getDeleteStatus(this.getRealm())
val message = deletableItem.getFailedDeleteMessage(status)
val builder = AlertDialog.Builder(requireContext())
.setMessage(message)
.setNegativeButton(R.string.ok, null)
builder.show()
}
}
}
/**
* Show undo snack bar
*/
private fun showUndoSnackBar() {
val message = String.format(getString(R.string.data_deleted))
this.coordinatorLayout?.let { view ->
snackBar = Snackbar.make(view, message, Snackbar.LENGTH_INDEFINITE)
snackBar?.setAction(R.string.cancel) {
getRealm().executeTransaction { realm ->
deletedItem?.let {
val item = realm.copyToRealmOrUpdate(it)
updateUIAfterUndoDeletion(item)
}
}
}
snackBar?.show()
}
}
/**
* Called once the object has been deleted
*/
open fun updateUIAfterDeletion(itemPosition: Int) {
dataListAdapter?.notifyItemRemoved(itemPosition)
}
/**
* Called once the object has been restored
*/
open fun updateUIAfterUndoDeletion(newItem: RealmObject) {
dataListAdapter?.notifyItemInserted(lastDeletedItemPosition)
}
}

@ -192,7 +192,8 @@ open class EditableDataFragment : RealmFragment(), RowRepresentableDelegate {
activity?.setResult(RESULT_OK, intent) activity?.setResult(RESULT_OK, intent)
activity?.finish() activity?.finish()
} else { } else {
val message = deletable.getFailedDeleteMessage() val status = deletable.getDeleteStatus(realm)
val message = deletable.getFailedDeleteMessage(status)
val builder = AlertDialog.Builder(requireContext()) val builder = AlertDialog.Builder(requireContext())
.setMessage(message) .setMessage(message)
.setNegativeButton(R.string.ok, null) .setNegativeButton(R.string.ok, null)

@ -10,7 +10,7 @@ import android.widget.Toast
import androidx.core.app.ActivityOptionsCompat import androidx.core.app.ActivityOptionsCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import com.google.android.material.chip.ChipGroup import com.google.android.material.tabs.TabLayout
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.Sort import io.realm.Sort
import io.realm.kotlin.where import io.realm.kotlin.where
@ -27,7 +27,6 @@ import net.pokeranalytics.android.ui.activity.SessionActivity
import net.pokeranalytics.android.ui.adapter.FeedSessionRowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.FeedSessionRowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.FeedTransactionRowRepresentableAdapter import net.pokeranalytics.android.ui.adapter.FeedTransactionRowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate
import net.pokeranalytics.android.ui.extensions.ChipGroupExtension
import net.pokeranalytics.android.ui.fragment.components.RealmFragment import net.pokeranalytics.android.ui.fragment.components.RealmFragment
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager
@ -128,14 +127,19 @@ class FeedFragment : RealmFragment(), RowRepresentableDelegate {
} }
} }
filters.setOnCheckedChangeListener(object : ChipGroupExtension.SingleSelectionOnCheckedListener() { tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onCheckedChanged(group: ChipGroup, checkedId: Int) { override fun onTabSelected(tab: TabLayout.Tab) {
super.onCheckedChanged(group, checkedId) when (tab.position) {
when (checkedId) { 0 -> recyclerView.adapter = feedSessionAdapter
R.id.filterSessions -> recyclerView.adapter = feedSessionAdapter 1 -> recyclerView.adapter = feedTransactionAdapter
R.id.filterTransactions -> recyclerView.adapter = feedTransactionAdapter
} }
} }
override fun onTabUnselected(tab: TabLayout.Tab) {
}
override fun onTabReselected(tab: TabLayout.Tab) {
}
}) })
} }
@ -182,9 +186,9 @@ class FeedFragment : RealmFragment(), RowRepresentableDelegate {
*/ */
private fun createNewSession(isTournament: Boolean) { private fun createNewSession(isTournament: Boolean) {
val sessionCount = this.feedSessionAdapter.realmResults.size // val sessionCount = this.feedSessionAdapter.realmResults.size
// if (!AppGuard.isProUser && sessionCount >= AppGuard.MAX_SESSIONS_BEFORE_REQUESTING_SUBSCRIPTION) { // && !BuildConfig.DEBUG // if (!AppGuard.isProUser && sessionCount >= AppGuard.MAX_SESSIONS_BEFORE_REQUESTING_SUBSCRIPTION) { // && !BuildConfig.DEBUG
//// Toast.makeText(context, "Please subscribe!", Toast.LENGTH_LONG).show() // Toast.makeText(context, "Please subscribe!", Toast.LENGTH_LONG).show()
// BillingActivity.newInstance(requireContext()) // BillingActivity.newInstance(requireContext())
// return // return
// } // }

@ -27,6 +27,7 @@ import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableDiffCallback import net.pokeranalytics.android.ui.view.RowRepresentableDiffCallback
import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager import net.pokeranalytics.android.ui.view.SmoothScrollLinearLayoutManager
import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow
import net.pokeranalytics.android.util.extensions.getNextMinuteInMilliseconds
import java.util.* import java.util.*
@ -47,7 +48,6 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate {
private val refreshTimer: Runnable = object : Runnable { private val refreshTimer: Runnable = object : Runnable {
override fun run() { override fun run() {
// Refresh header each 30 seconds
refreshTimer() refreshTimer()
handler.postDelayed(this, TIMER_DELAY) handler.postDelayed(this, TIMER_DELAY)
} }
@ -194,7 +194,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate {
floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f) floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f)
.setDuration(animationDuration) .setDuration(animationDuration)
.setInterpolator(OvershootInterpolator()).start() .setInterpolator(OvershootInterpolator()).start()
handler.postDelayed(refreshTimer, TIMER_DELAY) handler.postDelayed(refreshTimer, Date().getNextMinuteInMilliseconds())
} }
SessionState.STARTED -> { SessionState.STARTED -> {
sessionMenu?.findItem(R.id.restart)?.isVisible = true sessionMenu?.findItem(R.id.restart)?.isVisible = true
@ -203,7 +203,7 @@ class SessionFragment : RealmFragment(), RowRepresentableDelegate {
floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f) floatingActionButton.animate().scaleX(1f).scaleY(1f).alpha(1f)
.setDuration(animationDuration) .setDuration(animationDuration)
.setInterpolator(OvershootInterpolator()).start() .setInterpolator(OvershootInterpolator()).start()
handler.postDelayed(refreshTimer, TIMER_DELAY) handler.postDelayed(refreshTimer, Date().getNextMinuteInMilliseconds())
} }
SessionState.PAUSED -> { SessionState.PAUSED -> {
sessionMenu?.findItem(R.id.restart)?.isVisible = true sessionMenu?.findItem(R.id.restart)?.isVisible = true

@ -2,6 +2,7 @@ package net.pokeranalytics.android.ui.view.rowrepresentable
import android.content.Context import android.content.Context
import net.pokeranalytics.android.calculus.ComputedStat import net.pokeranalytics.android.calculus.ComputedStat
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
@ -15,7 +16,7 @@ class CustomizableRowRepresentable(
var value: String? = null, var value: String? = null,
var computedStat: ComputedStat? = null, var computedStat: ComputedStat? = null,
var isSelectable: Boolean? = false var isSelectable: Boolean? = false
) : RowRepresentable { ) : RowRepresentable, Identifiable {
override fun localizedTitle(context: Context): String { override fun localizedTitle(context: Context): String {
@ -30,4 +31,5 @@ class CustomizableRowRepresentable(
override val viewType: Int = customViewType?.ordinal ?: RowViewType.HEADER_TITLE.ordinal override val viewType: Int = customViewType?.ordinal ?: RowViewType.HEADER_TITLE.ordinal
override var id: String = ""
} }

@ -1,6 +1,33 @@
package net.pokeranalytics.android.ui.view.rowrepresentable package net.pokeranalytics.android.ui.view.rowrepresentable
import net.pokeranalytics.android.R
import net.pokeranalytics.android.ui.fragment.components.bottomsheet.BottomSheetType
import net.pokeranalytics.android.ui.view.DefaultEditDataSource import net.pokeranalytics.android.ui.view.DefaultEditDataSource
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
enum class TransactionTypeRow : RowRepresentable, DefaultEditDataSource enum class TransactionTypeRow : RowRepresentable, DefaultEditDataSource {
TRANSACTION_ADDITIVE;
override val resId: Int?
get() {
return when (this) {
TRANSACTION_ADDITIVE -> R.string.additive
}
}
override val viewType: Int
get() {
return when (this) {
TRANSACTION_ADDITIVE -> RowViewType.TITLE_SWITCH.ordinal
}
}
override val bottomSheetType: BottomSheetType
get() {
return when (this) {
TRANSACTION_ADDITIVE -> BottomSheetType.NONE
}
}
}

@ -141,3 +141,17 @@ fun Date.startOfYear() : Date {
calendar.set(Calendar.MONTH, 0) calendar.set(Calendar.MONTH, 0)
return calendar.time return calendar.time
} }
// Return the number of seconds until the next minute
fun Date.getNextMinuteInseconds() : Int {
return (getNextMinuteInMilliseconds() / 1000).toInt()
}
// Return the number of milliseconds until the next minute
fun Date.getNextMinuteInMilliseconds() : Long {
val calendar = Calendar.getInstance()
calendar.add(Calendar.MINUTE, 1)
calendar.set(Calendar.SECOND, 0)
calendar.set(Calendar.MILLISECOND, 0)
return calendar.time.time - this.time
}

@ -2,7 +2,7 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container" android:id="@+id/coordinatorLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container" android:id="@+id/container"
@ -16,45 +17,25 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<LinearLayout <com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" app:tabMode="fixed">
android:orientation="horizontal"
android:padding="8dp">
<com.google.android.material.chip.ChipGroup <com.google.android.material.tabs.TabItem
android:id="@+id/filters"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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:id="@+id/filterSessions"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:checked="true" android:text="@string/sessions"/>
android:text="@string/sessions" />
<com.google.android.material.chip.Chip <com.google.android.material.tabs.TabItem
android:id="@+id/filterTransactions" android:id="@+id/filterTransactions"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/operations" /> android:text="@string/operations"/>
</com.google.android.material.chip.ChipGroup>
</LinearLayout> </com.google.android.material.tabs.TabLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
@ -66,7 +47,7 @@
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/appBar" app:layout_constraintTop_toBottomOf="@+id/appBar"
tools:listitem="@layout/row_feed_session" /> tools:listitem="@layout/row_feed_session"/>
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/noSessionFound" android:id="@+id/noSessionFound"
@ -85,7 +66,7 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5" app:layout_constraintVertical_bias="0.5"
tools:visibility="visible" /> tools:visibility="visible"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/addButton" android:id="@+id/addButton"
@ -99,7 +80,7 @@
android:transitionName="floating_action_button" android:transitionName="floating_action_button"
app:fabSize="normal" app:fabSize="normal"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.LinearLayoutCompat <androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/disclaimerContainer" android:id="@+id/disclaimerContainer"
@ -121,7 +102,7 @@
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:text="@string/disclaimer" android:text="@string/disclaimer"
android:textSize="18sp" android:textSize="18sp"
tools:visibility="visible" /> tools:visibility="visible"/>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/disclaimerDismiss" android:id="@+id/disclaimerDismiss"
@ -131,7 +112,7 @@
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:text="@string/iunderstand" /> android:text="@string/iunderstand"/>
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>

@ -14,11 +14,12 @@
<string name="pro_upgrade">Devenez membre Pro</string> <string name="pro_upgrade">Devenez membre Pro</string>
<string name="pro_purchase">Passer Pro</string> <string name="pro_purchase">Passer Pro</string>
<string name="free_trial">d\'essai gratuit</string> <string name="free_trial">d\'essai gratuit</string>
<string name="bankroll_relationship_error_transactions">Cette bankroll contient au moins une transaction empêchant la suppression.</string>
<!-- Not translated --> <!-- Not translated -->
<string name="address">Adresse</string> <string name="address">Adresse</string>
<string name="suggestions">Suggestions de noms</string> <string name="suggestions">Suggestions de noms</string>
<string name="data_deleted" formatted="false">%s effacés</string> <string name="data_deleted">Élément effacé</string>
<string name="end_date_not_possible">La date de fin doit être après la date de début</string> <string name="end_date_not_possible">La date de fin doit être après la date de début</string>
<string name="save">Sauvegarder</string> <string name="save">Sauvegarder</string>
<string name="tournament_name">Nom du tournoi</string> <string name="tournament_name">Nom du tournoi</string>

@ -27,7 +27,7 @@
<string name="address">Address</string> <string name="address">Address</string>
<string name="suggestions">Naming suggestions</string> <string name="suggestions">Naming suggestions</string>
<string name="data_deleted" formatted="false">%s deleted</string> <string name="data_deleted">Data deleted</string>
<string name="end_date_not_possible">The end date should be after the start date</string> <string name="end_date_not_possible">The end date should be after the start date</string>
<string name="save">Save</string> <string name="save">Save</string>
<string name="tournament_name">Tournament name</string> <string name="tournament_name">Tournament name</string>
@ -39,6 +39,7 @@
<string name="duplicate_tournament_feature_error">A tournament feature with the same name already exists.</string> <string name="duplicate_tournament_feature_error">A tournament feature with the same name already exists.</string>
<string name="tournament_name_empty_field_error">A tournament name can not be empty.</string> <string name="tournament_name_empty_field_error">A tournament name can not be empty.</string>
<string name="duplicate_tournament_name_error">This name already exists.</string> <string name="duplicate_tournament_name_error">This name already exists.</string>
<string name="bankroll_relationship_error_transactions">One or more transactions are associated with this bankroll, please delete the linked transaction(s) first.</string>
<!-- Translated --> <!-- Translated -->

Loading…
Cancel
Save