Use viewmodels with LiveData.
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2022-12-24 12:12:19 +01:00
parent 5e0c80be17
commit 0e31908c7a
15 changed files with 555 additions and 407 deletions

View File

@@ -11,6 +11,11 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<service
android:name=".Tichu"
android:enabled="true"
android:exported="true"></service>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
@@ -22,6 +27,7 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<service <service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService" android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false" android:enabled="false"

View File

@@ -3,21 +3,22 @@ package me.zobrist.tichucounter
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.InputType
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.WindowManager import android.view.WindowManager
import android.view.inputmethod.InputMethodManager import androidx.activity.viewModels
import android.widget.ScrollView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat import androidx.core.os.LocaleListCompat
import androidx.core.widget.doOnTextChanged
import com.google.gson.Gson import com.google.gson.Gson
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import me.zobrist.tichucounter.databinding.ActivityMainBinding import me.zobrist.tichucounter.databinding.ActivityMainBinding
import me.zobrist.tichucounter.domain.History import me.zobrist.tichucounter.domain.History
import me.zobrist.tichucounter.domain.Round import me.zobrist.tichucounter.domain.Round
import me.zobrist.tichucounter.domain.Tichu
import me.zobrist.tichucounter.domain.getAbsoluteDifference
import me.zobrist.tichucounter.fragments.HistoryListViewModel
import me.zobrist.tichucounter.fragments.KeyboardViewModel
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@@ -28,8 +29,13 @@ class MainActivity : AppCompatActivity() {
@Inject @Inject
lateinit var history: History lateinit var history: History
lateinit var currentRound: Round private var currentRound: Round = Round(null, null)
private val keyboardViewModel: KeyboardViewModel by viewModels()
private val historyListViewModel: HistoryListViewModel by viewModels()
private var ignoreNextUpdate: Boolean = false
private var systemLocale = Locale.getDefault() private var systemLocale = Locale.getDefault()
@@ -41,19 +47,15 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
setSupportActionBar(binding.toolbar) setSupportActionBar(binding.toolbar)
binding.contentMain.keyboard.inputTeamA.setRawInputType(InputType.TYPE_NULL)
binding.contentMain.keyboard.inputTeamB.setRawInputType(InputType.TYPE_NULL)
binding.contentMain.keyboard.inputTeamA.requestFocus()
disableSubmitButton()
updateTheme(this.getSharedPreferences("Settings", MODE_PRIVATE).getInt("Theme", 2)) updateTheme(this.getSharedPreferences("Settings", MODE_PRIVATE).getInt("Theme", 2))
keepScreenOn( keepScreenOn(
this.getSharedPreferences("Settings", MODE_PRIVATE) this.getSharedPreferences("Settings", MODE_PRIVATE).getBoolean("Screen_On", false)
.getBoolean("Screen_On", false)
) )
val json = this.getSharedPreferences("Settings", MODE_PRIVATE) val json = this.getSharedPreferences("Settings", MODE_PRIVATE)
.getString("history", "{\"scores\":[]}") .getString("history", "{\"scores\":[]}")
history = Gson().fromJson(json, History::class.java) history = Gson().fromJson(json, History::class.java)
@@ -63,9 +65,58 @@ class MainActivity : AppCompatActivity() {
binding.contentMain.nameTeamB.setText( binding.contentMain.nameTeamB.setText(
this.getSharedPreferences("Settings", MODE_PRIVATE).getString("nameTeamB", "TeamB") this.getSharedPreferences("Settings", MODE_PRIVATE).getString("nameTeamB", "TeamB")
) )
updateView()
this.setListeners() keyboardViewModel.scoreA.observe(this, androidx.lifecycle.Observer { value ->
val tichu = Tichu()
val oldValue = currentRound.scoreA
currentRound.scoreA = value
if (ignoreNextUpdate) {
ignoreNextUpdate = false
} else {
if (currentRound.scoreA?.let { oldValue?.getAbsoluteDifference(it) } != 100) {
ignoreNextUpdate = true
currentRound.scoreB = tichu.calculateOtherScore(value)
keyboardViewModel.setScoreB(currentRound.scoreB)
}
keyboardViewModel.setSubmitButtonEnable(tichu.isValidRound(currentRound))
}
})
keyboardViewModel.scoreB.observe(this, androidx.lifecycle.Observer { value ->
val tichu = Tichu()
val oldValue = currentRound.scoreB
currentRound.scoreB = value
if (ignoreNextUpdate) {
ignoreNextUpdate = false
} else {
if (currentRound.scoreB?.let { oldValue?.getAbsoluteDifference(it) } != 100) {
ignoreNextUpdate = true
currentRound.scoreA = tichu.calculateOtherScore(value)
keyboardViewModel.setScoreA(currentRound.scoreA)
}
keyboardViewModel.setSubmitButtonEnable(tichu.isValidRound(currentRound))
}
})
keyboardViewModel.submitButtonClicked.observe(this, androidx.lifecycle.Observer {
historyListViewModel.logRound(currentRound)
keyboardViewModel.setScoreA(null)
keyboardViewModel.setScoreB(null)
})
historyListViewModel.totalScoreA.observe(this, androidx.lifecycle.Observer { value ->
binding.contentMain.scoreA.text = value.toString()
})
historyListViewModel.totalScoreB.observe(this, androidx.lifecycle.Observer { value ->
binding.contentMain.scoreB.text = value.toString()
})
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
@@ -79,264 +130,12 @@ class MainActivity : AppCompatActivity() {
} }
private fun setListeners() {
binding.contentMain.keyboard.inputTeamA.setOnFocusChangeListener { _, b ->
if (b) {
hideKeyboard()
}
}
binding.contentMain.keyboard.inputTeamB.setOnFocusChangeListener { _, b ->
if (b) {
hideKeyboard()
}
}
binding.contentMain.keyboard.inputTeamA.doOnTextChanged { text, _, _, _ ->
if (binding.contentMain.keyboard.inputTeamA.isFocused) {
if (binding.contentMain.keyboard.inputTeamA.text.isNotEmpty()) {
if (updateOnChange) {
currentRound = try {
Round(text.toString().toInt(), true)
} catch (e: java.lang.Exception) {
Round(1, 1)
}
binding.contentMain.keyboard.inputTeamB.setText(currentRound.scoreB.toString())
} else {
updateOnChange = true
}
} else {
binding.contentMain.keyboard.inputTeamA.text.clear()
binding.contentMain.keyboard.inputTeamB.text.clear()
}
}
if (currentRound.isValidRound() && binding.contentMain.keyboard.inputTeamA.text.isNotEmpty() && binding.contentMain.keyboard.inputTeamB.text.isNotEmpty()) {
enableSubmitButton()
} else {
disableSubmitButton()
}
}
binding.contentMain.keyboard.inputTeamB.doOnTextChanged { text, _, _, _ ->
if (binding.contentMain.keyboard.inputTeamB.isFocused) {
if (binding.contentMain.keyboard.inputTeamB.text.isNotEmpty()) {
if (updateOnChange) {
currentRound = try {
Round(text.toString().toInt(), false)
} catch (e: java.lang.Exception) {
Round(1, 1)
}
binding.contentMain.keyboard.inputTeamA.setText(currentRound.scoreA.toString())
} else {
updateOnChange = true
}
} else {
binding.contentMain.keyboard.inputTeamA.text.clear()
binding.contentMain.keyboard.inputTeamB.text.clear()
}
}
if (currentRound.isValidRound() && binding.contentMain.keyboard.inputTeamA.text.isNotEmpty() && binding.contentMain.keyboard.inputTeamB.text.isNotEmpty()) {
enableSubmitButton()
} else {
disableSubmitButton()
}
}
binding.contentMain.keyboard.buttonAdd100.setOnClickListener {
giveFocusToAIfNone()
if (binding.contentMain.keyboard.inputTeamA.isFocused) {
currentRound.scoreA = try {
binding.contentMain.keyboard.inputTeamA.text.toString().toInt() + 100
} catch (e: Exception) {
currentRound.scoreB = 0
binding.contentMain.keyboard.inputTeamB.setText(currentRound.scoreB.toString())
100
}
updateOnChange = false
binding.contentMain.keyboard.inputTeamA.setText(currentRound.scoreA.toString())
}
if (binding.contentMain.keyboard.inputTeamB.isFocused) {
currentRound.scoreB = try {
binding.contentMain.keyboard.inputTeamB.text.toString().toInt() + 100
} catch (e: Exception) {
currentRound.scoreA = 0
binding.contentMain.keyboard.inputTeamA.setText(currentRound.scoreA.toString())
100
}
updateOnChange = false
binding.contentMain.keyboard.inputTeamB.setText(currentRound.scoreB.toString())
}
}
binding.contentMain.keyboard.buttonSub100.setOnClickListener {
giveFocusToAIfNone()
if (binding.contentMain.keyboard.inputTeamA.isFocused) {
currentRound.scoreA = try {
binding.contentMain.keyboard.inputTeamA.text.toString().toInt() - 100
} catch (e: Exception) {
currentRound.scoreB = 0
binding.contentMain.keyboard.inputTeamB.setText(currentRound.scoreB.toString())
-100
}
updateOnChange = false
binding.contentMain.keyboard.inputTeamA.setText(currentRound.scoreA.toString())
}
if (binding.contentMain.keyboard.inputTeamB.isFocused) {
currentRound.scoreB = try {
binding.contentMain.keyboard.inputTeamB.text.toString().toInt() - 100
} catch (e: Exception) {
currentRound.scoreA = 0
binding.contentMain.keyboard.inputTeamA.setText(currentRound.scoreA.toString())
-100
}
updateOnChange = false
binding.contentMain.keyboard.inputTeamB.setText(currentRound.scoreB.toString())
}
}
binding.contentMain.keyboard.button0.setOnClickListener {
giveFocusToAIfNone()
appendToFocusedInput('0')
}
binding.contentMain.keyboard.button1.setOnClickListener {
giveFocusToAIfNone()
appendToFocusedInput('1')
}
binding.contentMain.keyboard.button2.setOnClickListener {
giveFocusToAIfNone()
appendToFocusedInput('2')
}
binding.contentMain.keyboard.button3.setOnClickListener {
giveFocusToAIfNone()
appendToFocusedInput('3')
}
binding.contentMain.keyboard.button4.setOnClickListener {
giveFocusToAIfNone()
appendToFocusedInput('4')
}
binding.contentMain.keyboard.button5.setOnClickListener {
giveFocusToAIfNone()
appendToFocusedInput('5')
}
binding.contentMain.keyboard.button6.setOnClickListener {
giveFocusToAIfNone()
appendToFocusedInput('6')
}
binding.contentMain.keyboard.button7.setOnClickListener {
giveFocusToAIfNone()
appendToFocusedInput('7')
}
binding.contentMain.keyboard.button8.setOnClickListener {
giveFocusToAIfNone()
appendToFocusedInput('8')
}
binding.contentMain.keyboard.button9.setOnClickListener {
giveFocusToAIfNone()
appendToFocusedInput('9')
}
binding.contentMain.keyboard.buttonInv.setOnClickListener {
val tempInt: Int
giveFocusToAIfNone()
if (binding.contentMain.keyboard.inputTeamA.isFocused) {
if (binding.contentMain.keyboard.inputTeamA.text.toString() == "-") {
binding.contentMain.keyboard.inputTeamA.text.clear()
} else if (binding.contentMain.keyboard.inputTeamA.text.isNotEmpty()) {
tempInt = binding.contentMain.keyboard.inputTeamA.text.toString().toInt() * -1
binding.contentMain.keyboard.inputTeamA.setText(tempInt.toString())
} else {
updateOnChange = false
appendToFocusedInput('-')
currentRound = Round(1, 1)
}
} else if (binding.contentMain.keyboard.inputTeamB.isFocused) {
if (binding.contentMain.keyboard.inputTeamB.text.toString() == "-") {
binding.contentMain.keyboard.inputTeamB.text.clear()
} else if (binding.contentMain.keyboard.inputTeamB.text.isNotEmpty()) {
tempInt = binding.contentMain.keyboard.inputTeamB.text.toString().toInt() * -1
binding.contentMain.keyboard.inputTeamB.setText(tempInt.toString())
} else {
updateOnChange = false
appendToFocusedInput('-')
currentRound = Round(1, 1)
}
}
}
binding.contentMain.keyboard.buttonBack.setOnClickListener {
giveFocusToAIfNone()
if (binding.contentMain.keyboard.inputTeamA.isFocused) {
if (binding.contentMain.keyboard.inputTeamA.text.isNotEmpty()) {
val string = binding.contentMain.keyboard.inputTeamA.text.toString()
binding.contentMain.keyboard.inputTeamA.setText(string.substring(0, string.length - 1))
}
} else if (binding.contentMain.keyboard.inputTeamB.isFocused) {
if (binding.contentMain.keyboard.inputTeamB.text.isNotEmpty()) {
val string = binding.contentMain.keyboard.inputTeamB.text.toString()
binding.contentMain.keyboard.inputTeamB.setText(string.substring(0, string.length - 1))
}
}
}
binding.contentMain.keyboard.submit.setOnClickListener {
giveFocusToAIfNone()
if (binding.contentMain.keyboard.inputTeamA.text.isNotEmpty() && binding.contentMain.keyboard.inputTeamB.text.isNotEmpty()) {
history.logRound(
Round(
binding.contentMain.keyboard.inputTeamA.text.toString().toInt(),
binding.contentMain.keyboard.inputTeamB.text.toString().toInt()
)
)
updateView()
binding.contentMain.keyboard.inputTeamA.text.clear()
binding.contentMain.keyboard.inputTeamB.text.clear()
disableSubmitButton()
binding.contentMain.scrollHistory.scrollViewHistory.fullScroll(ScrollView.FOCUS_DOWN)
}
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present. // Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu) menuInflater.inflate(R.menu.menu_main, menu)
menu.findItem(R.id.action_screenOn).isChecked = menu.findItem(R.id.action_screenOn).isChecked =
this.getSharedPreferences("Settings", MODE_PRIVATE) this.getSharedPreferences("Settings", MODE_PRIVATE).getBoolean("Screen_On", false)
.getBoolean("Screen_On", false)
return true return true
} }
@@ -345,14 +144,11 @@ class MainActivity : AppCompatActivity() {
return when (item.itemId) { return when (item.itemId) {
R.id.action_clear -> { R.id.action_clear -> {
val builder = AlertDialog.Builder(this) val builder = AlertDialog.Builder(this)
builder.setMessage(getString(R.string.confirmClear)) builder.setMessage(getString(R.string.confirmClear)).setTitle(R.string.clear)
.setTitle(R.string.clear) .setCancelable(false).setPositiveButton(getString(R.string.yes)) { dialog, _ ->
.setCancelable(false)
.setPositiveButton(getString(R.string.yes)) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
clearAll() clearAll()
} }.setNegativeButton(getString(R.string.no)) { dialog, _ ->
.setNegativeButton(getString(R.string.no)) { dialog, _ ->
dialog.cancel() dialog.cancel()
} }
@@ -360,7 +156,7 @@ class MainActivity : AppCompatActivity() {
true true
} }
R.id.action_undo -> { R.id.action_undo -> {
undoLastRound() historyListViewModel.revertLastRound()
true true
} }
R.id.action_theme -> { R.id.action_theme -> {
@@ -380,59 +176,11 @@ class MainActivity : AppCompatActivity() {
} }
} }
private fun hideKeyboard() {
val imm: InputMethodManager =
getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(currentFocus!!.windowToken, 0)
}
private fun giveFocusToAIfNone() {
if (!binding.contentMain.keyboard.inputTeamA.isFocused && !binding.contentMain.keyboard.inputTeamB.isFocused) {
binding.contentMain.keyboard.inputTeamA.requestFocus()
}
}
private fun undoLastRound() {
history.revertLastRound()
updateView()
}
private fun updateView() {
binding.contentMain.scoreA.text = history.getScoreA().toString()
binding.contentMain.scoreB.text = history.getScoreB().toString()
binding.contentMain.scrollHistory.historyA?.text = history.getHistoryA()
binding.contentMain.scrollHistory.historyB?.text = history.getHistoryB()
}
private fun clearAll() { private fun clearAll() {
binding.contentMain.scrollHistory.historyA?.text = ""
binding.contentMain.scrollHistory.historyB?.text = ""
binding.contentMain.keyboard.inputTeamA.text.clear()
binding.contentMain.keyboard.inputTeamB.text.clear()
binding.contentMain.scoreA.text = "0"
binding.contentMain.scoreB.text = "0"
history.clearAll() history.clearAll()
} }
private fun appendToFocusedInput(toAppend: Char) {
if (binding.contentMain.keyboard.inputTeamA.isFocused) {
binding.contentMain.keyboard.inputTeamA.text.append(toAppend)
} else if (binding.contentMain.keyboard.inputTeamB.isFocused) {
binding.contentMain.keyboard.inputTeamB.text.append(toAppend)
}
}
private fun enableSubmitButton() {
binding.contentMain.keyboard.submit?.imageAlpha = 255 // 0 being transparent and 255 being opaque
binding.contentMain.keyboard.submit?.isEnabled = true
}
private fun disableSubmitButton() {
binding.contentMain.keyboard.submit?.imageAlpha = 60 // 0 being transparent and 255 being opaque
binding.contentMain.keyboard.submit?.isEnabled = false
}
private fun chooseThemeDialog() { private fun chooseThemeDialog() {
@@ -444,8 +192,7 @@ class MainActivity : AppCompatActivity() {
getString(R.string.android_default_text) getString(R.string.android_default_text)
) )
val checkedItem = val checkedItem = this.getSharedPreferences("Settings", MODE_PRIVATE).getInt("Theme", 2)
this.getSharedPreferences("Settings", MODE_PRIVATE).getInt("Theme", 2)
val prefs = this.getSharedPreferences("Settings", MODE_PRIVATE).edit() val prefs = this.getSharedPreferences("Settings", MODE_PRIVATE).edit()
@@ -470,8 +217,7 @@ class MainActivity : AppCompatActivity() {
builder.setTitle(getString(R.string.choose_language_text)) builder.setTitle(getString(R.string.choose_language_text))
val languagesMap = mapOf( val languagesMap = mapOf(
getString(R.string.english) to "en", getString(R.string.english) to "en", getString(R.string.german) to "de"
getString(R.string.german) to "de"
) )
val languagesDisplayKeys = languagesMap.keys.toTypedArray() val languagesDisplayKeys = languagesMap.keys.toTypedArray()

View File

@@ -11,7 +11,7 @@ class History {
fun getScoreA(): Int { fun getScoreA(): Int {
var tempScore = 0 var tempScore = 0
scores.forEach { scores.forEach {
tempScore += it.scoreA tempScore += it.scoreA!!
} }
return tempScore return tempScore
} }
@@ -19,7 +19,7 @@ class History {
fun getScoreB(): Int { fun getScoreB(): Int {
var tempScore = 0 var tempScore = 0
scores.forEach { scores.forEach {
tempScore += it.scoreB tempScore += it.scoreB!!
} }
return tempScore return tempScore
} }

View File

@@ -1,5 +1,8 @@
package me.zobrist.tichucounter.domain package me.zobrist.tichucounter.domain
import kotlin.math.abs
fun Int.isMultipleOf5(): Boolean { fun Int.isMultipleOf5(): Boolean {
return (this % 5) == 0 return (this % 5) == 0
} }
@@ -7,3 +10,7 @@ fun Int.isMultipleOf5(): Boolean {
fun Int.isMultipleOf100(): Boolean { fun Int.isMultipleOf100(): Boolean {
return (this % 100) == 0 return (this % 100) == 0
} }
fun Int.getAbsoluteDifference(other: Int): Int {
return abs(this - other)
}

View File

@@ -2,36 +2,5 @@ package me.zobrist.tichucounter.domain
import java.io.Serializable import java.io.Serializable
class Round() : Serializable { data class Round(var scoreA: Int?, var scoreB: Int?) : Serializable {
var scoreA: Int = 0
var scoreB: Int = 0
constructor(score: Int, isScoreA: Boolean) : this() {
if (isScoreA) {
scoreA = score
scoreB = calculateOtherScore(scoreA)
} else {
scoreB = score
scoreA = calculateOtherScore(scoreB)
}
}
constructor(scoreA: Int, scoreB: Int) : this() {
this.scoreA = scoreA
this.scoreB = scoreB
}
fun calculateOtherScore(score: Int): Int {
if (score.isMultipleOf100() && score != 0) {
return 0
}
if (score in 101..125) {
return 0 - (score % 100)
}
return 100 - (score % 100)
}
fun isValidRound(): Boolean {
return (scoreA.isMultipleOf5()) && scoreB.isMultipleOf5() && (scoreA + scoreB).isMultipleOf100()
}
} }

View File

@@ -0,0 +1,27 @@
package me.zobrist.tichucounter.domain
class Tichu {
fun calculateOtherScore(score: Int?): Int? {
if (score == null) {
return null
}
if (!score.isMultipleOf5()) {
return null
}
if (score.isMultipleOf100() && score != 0) {
return 0
}
if (score in 101..125) {
return 0 - (score % 100)
}
return 100 - (score % 100)
}
fun isValidRound(round: Round): Boolean {
if (round.scoreA == null || round.scoreB == null) {
return false
}
return (round.scoreA!!.isMultipleOf5()) && round.scoreB!!.isMultipleOf5() && (round.scoreA!! + round.scoreB!!).isMultipleOf100()
}
}

View File

@@ -1,32 +1,58 @@
package me.zobrist.tichucounter.fragments package me.zobrist.tichucounter.fragments
import androidx.lifecycle.ViewModelProvider import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import android.text.InputType
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 me.zobrist.tichucounter.R import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import dagger.hilt.android.AndroidEntryPoint
import me.zobrist.tichucounter.databinding.FragmentHistoryListBinding
import me.zobrist.tichucounter.databinding.FragmentKeyboardBinding
class HistoryList : Fragment() { class HistoryList : Fragment() {
private var _binding: FragmentHistoryListBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
companion object { companion object {
fun newInstance() = HistoryList() fun newInstance() = HistoryList()
} }
private lateinit var viewModel: HistoryListViewModel private val viewModel: HistoryListViewModel by activityViewModels()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
return inflater.inflate(R.layout.fragment_history_list, container, false) _binding = FragmentHistoryListBinding.inflate(inflater, container, false)
return binding.root
} }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this).get(HistoryListViewModel::class.java)
// TODO: Use the ViewModel viewModel.historyA.observe(viewLifecycleOwner, Observer { text ->
binding.historyA.text = text
})
viewModel.historyB.observe(viewLifecycleOwner, Observer { text ->
binding.historyB.text = text
})
} }
} }

View File

@@ -1,7 +1,100 @@
package me.zobrist.tichucounter.fragments package me.zobrist.tichucounter.fragments
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import me.zobrist.tichucounter.domain.Round
class HistoryListViewModel : ViewModel() { class HistoryListViewModel : ViewModel() {
// TODO: Implement the ViewModel // TODO: Implement the ViewModel
private var scores = ArrayList<Round>()
private val _totalScoreA: MutableLiveData<Int> = MutableLiveData<Int>()
private val _totalScoreB: MutableLiveData<Int> = MutableLiveData<Int>()
private val _historyA: MutableLiveData<String> = MutableLiveData<String>()
private val _historyB: MutableLiveData<String> = MutableLiveData<String>()
val totalScoreA: LiveData<Int>
get() {
return _totalScoreA
} }
val totalScoreB: LiveData<Int>
get() {
return _totalScoreB
}
val historyA: LiveData<String>
get() {
return _historyA
}
val historyB: LiveData<String>
get() {
return _historyB
}
private fun getScoreA() {
var tempScore = 0
scores.forEach {
tempScore += it.scoreA!!
}
_totalScoreA.value = tempScore
}
private fun getScoreB() {
var tempScore = 0
scores.forEach {
tempScore += it.scoreB!!
}
_totalScoreB.value = tempScore
}
private fun getHistoryA() {
var tempHistory = String()
scores.forEach {
tempHistory += it.scoreA.toString() + "\n"
}
_historyA.value = tempHistory
}
private fun getHistoryB() {
var tempHistory = String()
scores.forEach {
tempHistory += it.scoreB.toString() + "\n"
}
_historyB.value = tempHistory
}
fun logRound(round: Round) {
scores.add(round.copy())
getHistoryA()
getHistoryB()
getScoreA()
getScoreB()
}
fun revertLastRound() {
if (scores.isNotEmpty()) {
scores.removeAt(scores.size - 1)
}
getHistoryA()
getHistoryB()
getScoreA()
getScoreB()
}
fun clearAll() {
scores.clear()
}
fun isEmpty(): Boolean {
return scores.isEmpty()
}
}

View File

@@ -1,35 +1,265 @@
package me.zobrist.tichucounter.fragments package me.zobrist.tichucounter.fragments
import androidx.lifecycle.ViewModelProvider import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import android.text.InputType
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 android.widget.ScrollView import android.view.inputmethod.InputMethodManager
import androidx.core.widget.doOnTextChanged import android.widget.EditText
import me.zobrist.tichucounter.R import androidx.fragment.app.Fragment
import me.zobrist.tichucounter.domain.Round import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import dagger.hilt.android.AndroidEntryPoint
import me.zobrist.tichucounter.databinding.FragmentKeyboardBinding
class Keyboard : Fragment() { class Keyboard : Fragment() {
private var _binding: FragmentKeyboardBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private var unhandledNegation: Boolean = false
companion object { companion object {
fun newInstance() = Keyboard() fun newInstance() = Keyboard()
} }
private lateinit var viewModel: KeyboardViewModel private val viewModel: KeyboardViewModel by activityViewModels()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
return inflater.inflate(R.layout.fragment_keyboard, container, false) _binding = FragmentKeyboardBinding.inflate(inflater, container, false)
return binding.root
} }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProvider(this).get(KeyboardViewModel::class.java)
// TODO: Use the ViewModel binding.inputTeamA.setRawInputType(InputType.TYPE_NULL)
binding.inputTeamB.setRawInputType(InputType.TYPE_NULL)
binding.inputTeamA.requestFocus()
disableSubmitButton()
viewModel.enableSubmitButton.observe(viewLifecycleOwner, Observer { enabled ->
if (enabled) enableSubmitButton() else disableSubmitButton()
})
viewModel.scoreA.observe(viewLifecycleOwner, Observer { value ->
updateScore(binding.inputTeamA, value)
})
viewModel.scoreB.observe(viewLifecycleOwner, Observer { value ->
updateScore(binding.inputTeamB, value)
})
setListeners()
} }
private fun setListeners() {
binding.inputTeamA.setOnFocusChangeListener { _, b ->
if (b) {
hideKeyboard()
}
}
binding.inputTeamB.setOnFocusChangeListener { _, b ->
if (b) {
hideKeyboard()
}
}
binding.buttonAdd100.setOnClickListener {
var value = getActiveValue()
value = if (value != null) {
value + 100
} else {
100
}
setActiveValue(value)
}
binding.buttonSub100.setOnClickListener {
var value = getActiveValue()
value = if (value != null) {
value!! - 100
} else {
-100
}
setActiveValue(value)
}
binding.button0.setOnClickListener {
appendToFocusedScore(0)
}
binding.button1.setOnClickListener {
appendToFocusedScore(1)
}
binding.button2.setOnClickListener {
appendToFocusedScore(2)
}
binding.button3.setOnClickListener {
appendToFocusedScore(3)
}
binding.button4.setOnClickListener {
appendToFocusedScore(4)
}
binding.button5.setOnClickListener {
appendToFocusedScore(5)
}
binding.button6.setOnClickListener {
appendToFocusedScore(6)
}
binding.button7.setOnClickListener {
appendToFocusedScore(7)
}
binding.button8.setOnClickListener {
appendToFocusedScore(8)
}
binding.button9.setOnClickListener {
appendToFocusedScore(9)
}
binding.buttonInv.setOnClickListener {
var value = getActiveValue()
if (value == null) {
if (getActiveText() == "-") {
setActiveText("")
unhandledNegation = false
} else {
setActiveText("-")
unhandledNegation = true
}
} else {
value = value?.times(-1)
setActiveValue(value)
}
}
binding.buttonBack.setOnClickListener {
var value = getActiveValue()
if (value != null) {
value = try {
value.toString().dropLast(1).toInt()
} catch (e: Exception) {
null
}
}
setActiveValue(value)
}
binding.submit.setOnClickListener {
viewModel.submitButtonClicked()
}
}
private fun hideKeyboard() {
val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
imm?.hideSoftInputFromWindow(view?.windowToken, 0)
}
private fun giveFocusToAIfNone() {
if (!binding.inputTeamA.isFocused && !binding.inputTeamB.isFocused) {
binding.inputTeamA.requestFocus()
}
}
private fun updateScore(field: EditText, score: Int?) {
if (score == null) {
field.setText("")
return
}
var text = try {
score.toString()
} catch (e: Exception) {
""
}
field.setText(text)
}
private fun appendToFocusedScore(toAppend: Int) {
var value = getActiveValue()
if (value != null) {
value = value.times(10)
value = value.plus(toAppend)
} else {
value = toAppend
if (unhandledNegation) {
value = value.times(-1)
unhandledNegation = false
}
}
setActiveValue(value)
}
private fun getActiveValue(): Int? {
giveFocusToAIfNone()
if (binding.inputTeamA.isFocused) {
return viewModel.scoreA.value
}
return viewModel.scoreB.value
}
private fun setActiveValue(value: Int?) {
giveFocusToAIfNone()
if (binding.inputTeamA.isFocused) {
viewModel.setScoreA(value)
} else {
viewModel.setScoreB(value)
}
}
private fun getActiveText(): String {
giveFocusToAIfNone()
if (binding.inputTeamA.isFocused) {
return binding.inputTeamA.text.toString()
}
return binding.inputTeamB.text.toString()
}
private fun setActiveText(value: String) {
giveFocusToAIfNone()
if (binding.inputTeamA.isFocused) {
binding.inputTeamA.setText(value)
} else {
binding.inputTeamB.setText(value)
}
}
private fun enableSubmitButton() {
binding.submit.imageAlpha = 255 // 0 being transparent and 255 being opaque
binding.submit.isEnabled = true
}
private fun disableSubmitButton() {
binding.submit.imageAlpha = 60 // 0 being transparent and 255 being opaque
binding.submit.isEnabled = false
}
} }

View File

@@ -1,7 +1,50 @@
package me.zobrist.tichucounter.fragments package me.zobrist.tichucounter.fragments
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
class KeyboardViewModel: ViewModel() { class KeyboardViewModel: ViewModel() {
// TODO: Implement the ViewModel private val _scoreA: MutableLiveData<Int?> = MutableLiveData<Int?>()
private val _scoreB: MutableLiveData<Int?> = MutableLiveData<Int?>()
private val _enableSubmitButton: MutableLiveData<Boolean> = MutableLiveData<Boolean>()
private val _submitButtonClicked: MutableLiveData<Boolean> = MutableLiveData<Boolean>()
val scoreA: LiveData<Int?>
get() {
return _scoreA
}
val scoreB: LiveData<Int?>
get() {
return _scoreB
}
val enableSubmitButton: LiveData<Boolean>
get() {
return _enableSubmitButton
}
val submitButtonClicked: LiveData<Boolean>
get() {
return _submitButtonClicked
}
fun setScoreA(score: Int?) {
_scoreA.value = score
}
fun setScoreB(score: Int?) {
_scoreB.value = score
}
fun setSubmitButtonEnable(enabled: Boolean) {
_enableSubmitButton.value = enabled
}
fun submitButtonClicked() {
_submitButtonClicked.value = true
}
} }

View File

@@ -4,6 +4,4 @@ import android.app.Application
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp @HiltAndroidApp
class TichuCounterApplication : Application() { class TichuCounterApplication : Application()
}

View File

@@ -1,6 +1,7 @@
<?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"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:visibility="visible" android:visibility="visible"
@@ -82,13 +83,14 @@
</LinearLayout> </LinearLayout>
<include <fragment
android:id="@+id/scrollHistory" android:id="@+id/scrollHistory"
layout="@layout/fragment_history_list" android:name="me.zobrist.tichucounter.fragments.HistoryList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:clickable="false"/> android:clickable="false"
tools:layout="@layout/fragment_history_list" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@@ -101,12 +103,15 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/left"> app:layout_constraintStart_toEndOf="@+id/left">
<include <fragment
android:id="@+id/keyboard" android:id="@+id/keyboard"
layout="@layout/fragment_keyboard" android:name="me.zobrist.tichucounter.fragments.Keyboard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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="@layout/fragment_keyboard" />
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -87,22 +87,23 @@
android:layout_height="1dp" android:layout_height="1dp"
android:layout_weight="1" android:layout_weight="1"
android:background="?android:attr/listDivider" android:background="?android:attr/listDivider"
app:layout_constraintTop_toBottomOf="@id/viewScore"
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/viewScore"
tools:layout_editor_absoluteY="50dp" /> tools:layout_editor_absoluteY="50dp" />
<include <fragment
android:id="@+id/scrollHistory" android:id="@+id/scrollHistory"
layout="@layout/fragment_history_list" android:name="me.zobrist.tichucounter.fragments.HistoryList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/dividerTop"
app:layout_constraintBottom_toTopOf="@id/dividerBottom" app:layout_constraintBottom_toTopOf="@id/dividerBottom"
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/dividerTop"
tools:layout="@layout/fragment_history_list" />
<View <View
android:id="@+id/dividerBottom" android:id="@+id/dividerBottom"
@@ -113,16 +114,15 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<fragment
<include
android:id="@+id/keyboard" android:id="@+id/keyboard"
layout="@layout/fragment_keyboard" android:name="me.zobrist.tichucounter.fragments.Keyboard"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
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="@layout/fragment_keyboard" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@@ -43,5 +42,4 @@
</ScrollView> </ScrollView>
</FrameLayout> </FrameLayout>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragments.Keyboard"> tools:context=".fragments.Keyboard">
<LinearLayout <LinearLayout