commit
cd3d0008a6
14 changed files with 241 additions and 59 deletions
|
@ -9,8 +9,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
// versionCode and versionName must be hardcoded to support F-droid
|
||||
versionCode 702051
|
||||
versionName '7.2.5'
|
||||
versionCode 702061
|
||||
versionName '7.2.6'
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
multiDexEnabled true
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
package dev.lucasnlm.antimine.stats
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Observer
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.common.level.repository.IStatsRepository
|
||||
import dev.lucasnlm.antimine.stats.model.StatsModel
|
||||
import dev.lucasnlm.antimine.stats.viewmodel.StatsViewModel
|
||||
import kotlinx.android.synthetic.main.activity_stats.*
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
|
@ -22,25 +26,76 @@ class StatsActivity : AppCompatActivity(R.layout.activity_stats) {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
refreshStats(StatsViewModel.emptyStats)
|
||||
|
||||
viewModel.statsObserver.observe(
|
||||
this,
|
||||
Observer {
|
||||
minesCount.text = it.mines.toString()
|
||||
totalTime.text = formatTime(it.duration)
|
||||
averageTime.text = formatTime(it.averageDuration)
|
||||
totalGames.text = it.totalGames.toString()
|
||||
performance.text = formatPercentage(100.0 * it.victory / it.totalGames)
|
||||
openAreas.text = it.openArea.toString()
|
||||
victory.text = it.victory.toString()
|
||||
defeat.text = (it.totalGames - it.victory).toString()
|
||||
refreshStats(it)
|
||||
}
|
||||
)
|
||||
|
||||
GlobalScope.launch {
|
||||
viewModel.loadStats(statsRepository)
|
||||
viewModel.loadStats()
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshStats(stats: StatsModel) {
|
||||
if (stats.totalGames > 0) {
|
||||
minesCount.text = stats.mines.toString()
|
||||
totalTime.text = formatTime(stats.duration)
|
||||
averageTime.text = formatTime(stats.averageDuration)
|
||||
totalGames.text = stats.totalGames.toString()
|
||||
performance.text = formatPercentage(100.0 * stats.victory / stats.totalGames)
|
||||
openAreas.text = stats.openArea.toString()
|
||||
victory.text = stats.victory.toString()
|
||||
defeat.text = (stats.totalGames - stats.victory).toString()
|
||||
} else {
|
||||
val emptyText = "-"
|
||||
totalGames.text = "0"
|
||||
minesCount.text = emptyText
|
||||
totalTime.text = emptyText
|
||||
averageTime.text = emptyText
|
||||
performance.text = emptyText
|
||||
openAreas.text = emptyText
|
||||
victory.text = emptyText
|
||||
defeat.text = emptyText
|
||||
}
|
||||
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
viewModel.statsObserver.value?.let {
|
||||
if (it.totalGames > 0) {
|
||||
menuInflater.inflate(R.menu.stats_menu, menu)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == R.id.delete) {
|
||||
confirmAndDelete()
|
||||
true
|
||||
} else {
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun confirmAndDelete() {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.are_you_sure)
|
||||
.setMessage(R.string.delete_all_message)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete_all) { _, _ ->
|
||||
GlobalScope.launch {
|
||||
viewModel.deleteAll()
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun formatPercentage(value: Double) =
|
||||
String.format("%.2f%%", value)
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
package dev.lucasnlm.antimine.stats.viewmodel
|
||||
|
||||
import androidx.hilt.lifecycle.ViewModelInject
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dev.lucasnlm.antimine.common.level.repository.IStatsRepository
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import dev.lucasnlm.antimine.stats.model.StatsModel
|
||||
|
||||
class StatsViewModel : ViewModel() {
|
||||
class StatsViewModel @ViewModelInject constructor(
|
||||
private val statsRepository: IStatsRepository,
|
||||
private val preferenceRepository: IPreferencesRepository
|
||||
) : ViewModel() {
|
||||
val statsObserver = MutableLiveData<StatsModel>()
|
||||
|
||||
suspend fun getStatsModel(statsRepository: IStatsRepository): StatsModel? {
|
||||
val stats = statsRepository.getAllStats()
|
||||
suspend fun getStatsModel(): StatsModel? {
|
||||
val minId = preferenceRepository.getStatsBase()
|
||||
val stats = statsRepository.getAllStats(minId)
|
||||
val statsCount = stats.count()
|
||||
|
||||
return if (statsCount > 0) {
|
||||
|
@ -27,15 +33,27 @@ class StatsViewModel : ViewModel() {
|
|||
}
|
||||
result.copy(averageDuration = result.duration / result.totalGames)
|
||||
} else {
|
||||
StatsModel(0, 0, 0, 0, 0, 0)
|
||||
emptyStats
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadStats(statsRepository: IStatsRepository) {
|
||||
getStatsModel(statsRepository)?.let {
|
||||
suspend fun deleteAll() {
|
||||
statsRepository.getAllStats(0).lastOrNull()?.let {
|
||||
preferenceRepository.updateStatsBase(it.uid + 1)
|
||||
}
|
||||
|
||||
statsObserver.postValue(emptyStats)
|
||||
}
|
||||
|
||||
suspend fun loadStats() {
|
||||
getStatsModel()?.let {
|
||||
if (it.totalGames > 0) {
|
||||
statsObserver.postValue(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val emptyStats = StatsModel(0, 0, 0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
|
10
app/src/main/res/drawable/delete.xml
Normal file
10
app/src/main/res/drawable/delete.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V9c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v10zM18,4h-2.5l-0.71,-0.71c-0.18,-0.18 -0.44,-0.29 -0.7,-0.29H9.91c-0.26,0 -0.52,0.11 -0.7,0.29L8.5,4H6c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h12c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z"/>
|
||||
</vector>
|
13
app/src/main/res/menu/stats_menu.xml
Normal file
13
app/src/main/res/menu/stats_menu.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
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"
|
||||
tools:context=".level.LevelActivity">
|
||||
|
||||
<item
|
||||
android:id="@+id/delete"
|
||||
android:icon="@drawable/delete"
|
||||
android:title="@string/delete_all"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
|
@ -2,9 +2,13 @@ package dev.lucasnlm.antimine.stats.viewmodel
|
|||
|
||||
import dev.lucasnlm.antimine.common.level.database.models.Stats
|
||||
import dev.lucasnlm.antimine.common.level.repository.MemoryStatsRepository
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runBlockingTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
|
@ -14,63 +18,132 @@ class StatsViewModelTest {
|
|||
Stats(1, 1200, 24, 0, 10, 10, 20)
|
||||
)
|
||||
|
||||
private val prefsRepository: IPreferencesRepository = mockk()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
every { prefsRepository.getStatsBase() } returns 0
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStatsTotalGames() = runBlockingTest {
|
||||
val viewModel = StatsViewModel()
|
||||
val statsModel = viewModel.getStatsModel(MemoryStatsRepository(listOfStats.toMutableList()))
|
||||
val repository = MemoryStatsRepository(listOfStats.toMutableList())
|
||||
val viewModel = StatsViewModel(repository, prefsRepository)
|
||||
val statsModel = viewModel.getStatsModel()
|
||||
assertEquals(2, statsModel?.totalGames)
|
||||
}
|
||||
|
||||
val emptyStatsModel = viewModel.getStatsModel(MemoryStatsRepository())
|
||||
assertEquals(0, emptyStatsModel?.totalGames)
|
||||
@Test
|
||||
fun testStatsTotalGamesWithBase() = runBlockingTest {
|
||||
val repository = MemoryStatsRepository(listOfStats.toMutableList())
|
||||
val viewModel = StatsViewModel(repository, prefsRepository)
|
||||
|
||||
every { prefsRepository.getStatsBase() } returns 0
|
||||
val statsModelBase0 = viewModel.getStatsModel()
|
||||
assertEquals(2, statsModelBase0?.totalGames)
|
||||
|
||||
every { prefsRepository.getStatsBase() } returns 1
|
||||
val statsModelBase1 = viewModel.getStatsModel()
|
||||
assertEquals(1, statsModelBase1?.totalGames)
|
||||
|
||||
every { prefsRepository.getStatsBase() } returns 2
|
||||
val statsModelBase2 = viewModel.getStatsModel()
|
||||
assertEquals(0, statsModelBase2?.totalGames)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStatsTotalGamesEmpty() = runBlockingTest {
|
||||
val repository = MemoryStatsRepository(mutableListOf())
|
||||
val viewModel = StatsViewModel(repository, prefsRepository)
|
||||
val statsModel = viewModel.getStatsModel()
|
||||
assertEquals(0, statsModel?.totalGames)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStatsDuration() = runBlockingTest {
|
||||
val viewModel = StatsViewModel()
|
||||
val statsModel = viewModel.getStatsModel(MemoryStatsRepository(listOfStats.toMutableList()))
|
||||
assertEquals(2200L, statsModel?.duration)
|
||||
val repository = MemoryStatsRepository(listOfStats.toMutableList())
|
||||
val viewModel = StatsViewModel(repository, prefsRepository)
|
||||
val statsModel = viewModel.getStatsModel()
|
||||
|
||||
val emptyStatsModel = viewModel.getStatsModel(MemoryStatsRepository())
|
||||
assertEquals(0L, emptyStatsModel?.duration)
|
||||
assertEquals(2200L, statsModel?.duration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStatsDurationEmpty() = runBlockingTest {
|
||||
val repository = MemoryStatsRepository(mutableListOf())
|
||||
val viewModel = StatsViewModel(repository, prefsRepository)
|
||||
val statsModel = viewModel.getStatsModel()
|
||||
|
||||
assertEquals(0L, statsModel?.duration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStatsAverageDuration() = runBlockingTest {
|
||||
val viewModel = StatsViewModel()
|
||||
val statsModel = viewModel.getStatsModel(MemoryStatsRepository(listOfStats.toMutableList()))
|
||||
assertEquals(1100L, statsModel?.averageDuration)
|
||||
val repository = MemoryStatsRepository(listOfStats.toMutableList())
|
||||
val viewModel = StatsViewModel(repository, prefsRepository)
|
||||
val statsModel = viewModel.getStatsModel()
|
||||
|
||||
val emptyStatsModel = viewModel.getStatsModel(MemoryStatsRepository(mutableListOf()))
|
||||
assertEquals(0L, emptyStatsModel?.averageDuration)
|
||||
assertEquals(1100L, statsModel?.averageDuration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStatsAverageDurationEmpty() = runBlockingTest {
|
||||
val repository = MemoryStatsRepository(mutableListOf())
|
||||
val viewModel = StatsViewModel(repository, prefsRepository)
|
||||
val statsModel = viewModel.getStatsModel()
|
||||
|
||||
assertEquals(0L, statsModel?.averageDuration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStatsMines() = runBlockingTest {
|
||||
val viewModel = StatsViewModel()
|
||||
val statsModel = viewModel.getStatsModel(MemoryStatsRepository(listOfStats.toMutableList()))
|
||||
val repository = MemoryStatsRepository(listOfStats.toMutableList())
|
||||
val viewModel = StatsViewModel(repository, prefsRepository)
|
||||
val statsModel = viewModel.getStatsModel()
|
||||
assertEquals(34, statsModel?.mines)
|
||||
}
|
||||
|
||||
val emptyStatsModel = viewModel.getStatsModel(MemoryStatsRepository(mutableListOf()))
|
||||
assertEquals(0, emptyStatsModel?.mines)
|
||||
@Test
|
||||
fun testStatsMinesEmpty() = runBlockingTest {
|
||||
val repository = MemoryStatsRepository(mutableListOf())
|
||||
val viewModel = StatsViewModel(repository, prefsRepository)
|
||||
val statsModel = viewModel.getStatsModel()
|
||||
assertEquals(0, statsModel?.mines)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVictory() = runBlockingTest {
|
||||
val viewModel = StatsViewModel()
|
||||
val statsModel = viewModel.getStatsModel(MemoryStatsRepository(listOfStats.toMutableList()))
|
||||
assertEquals(1, statsModel?.victory)
|
||||
val repository = MemoryStatsRepository(listOfStats.toMutableList())
|
||||
val viewModel = StatsViewModel(repository, prefsRepository)
|
||||
val statsModel = viewModel.getStatsModel()
|
||||
|
||||
val emptyStatsModel = viewModel.getStatsModel(MemoryStatsRepository(mutableListOf()))
|
||||
assertEquals(0, emptyStatsModel?.victory)
|
||||
assertEquals(1, statsModel?.victory)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVictoryEmpty() = runBlockingTest {
|
||||
val repository = MemoryStatsRepository(mutableListOf())
|
||||
val viewModel = StatsViewModel(repository, prefsRepository)
|
||||
val statsModel = viewModel.getStatsModel()
|
||||
|
||||
assertEquals(0, statsModel?.victory)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOpenArea() = runBlockingTest {
|
||||
val viewModel = StatsViewModel()
|
||||
val statsModel = viewModel.getStatsModel(MemoryStatsRepository(listOfStats.toMutableList()))
|
||||
assertEquals(110, statsModel?.openArea)
|
||||
val repository = MemoryStatsRepository(listOfStats.toMutableList())
|
||||
val viewModel = StatsViewModel(repository, prefsRepository)
|
||||
val statsModel = viewModel.getStatsModel()
|
||||
|
||||
val emptyStatsModel = viewModel.getStatsModel(MemoryStatsRepository(mutableListOf()))
|
||||
assertEquals(0, emptyStatsModel?.openArea)
|
||||
assertEquals(110, statsModel?.openArea)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOpenAreaEmpty() = runBlockingTest {
|
||||
val repository = MemoryStatsRepository(mutableListOf())
|
||||
val viewModel = StatsViewModel(repository, prefsRepository)
|
||||
val statsModel = viewModel.getStatsModel()
|
||||
|
||||
assertEquals(0, statsModel?.openArea)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
// versionCode and versionName must be hardcoded to support F-droid
|
||||
versionCode 702051
|
||||
versionName '7.2.5'
|
||||
versionCode 702061
|
||||
versionName '7.2.6'
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
|
|
@ -8,8 +8,8 @@ import dev.lucasnlm.antimine.common.level.database.models.Stats
|
|||
|
||||
@Dao
|
||||
interface StatsDao {
|
||||
@Query("SELECT * FROM stats")
|
||||
suspend fun getAll(): List<Stats>
|
||||
@Query("SELECT * FROM stats WHERE stats.uid >= :minId")
|
||||
suspend fun getAll(minId: Int = 0): List<Stats>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertAll(vararg stats: Stats): LongArray
|
||||
|
|
|
@ -4,15 +4,15 @@ import dev.lucasnlm.antimine.common.level.database.dao.StatsDao
|
|||
import dev.lucasnlm.antimine.common.level.database.models.Stats
|
||||
|
||||
interface IStatsRepository {
|
||||
suspend fun getAllStats(): List<Stats>
|
||||
suspend fun getAllStats(minId: Int): List<Stats>
|
||||
suspend fun addStats(stats: Stats): Long?
|
||||
}
|
||||
|
||||
class StatsRepository(
|
||||
private val statsDao: StatsDao
|
||||
) : IStatsRepository {
|
||||
override suspend fun getAllStats(): List<Stats> {
|
||||
return statsDao.getAll()
|
||||
override suspend fun getAllStats(minId: Int): List<Stats> {
|
||||
return statsDao.getAll(minId)
|
||||
}
|
||||
|
||||
override suspend fun addStats(stats: Stats): Long? {
|
||||
|
@ -23,7 +23,7 @@ class StatsRepository(
|
|||
class MemoryStatsRepository(
|
||||
private val memoryStats: MutableList<Stats> = mutableListOf()
|
||||
) : IStatsRepository {
|
||||
override suspend fun getAllStats(): List<Stats> = memoryStats.toList()
|
||||
override suspend fun getAllStats(minId: Int): List<Stats> = memoryStats.filter { it.uid >= minId }
|
||||
|
||||
override suspend fun addStats(stats: Stats): Long? {
|
||||
memoryStats.add(stats)
|
||||
|
|
|
@ -15,6 +15,9 @@ interface IPreferencesRepository {
|
|||
fun controlStyle(): ControlStyle
|
||||
fun useControlStyle(controlStyle: ControlStyle)
|
||||
|
||||
fun updateStatsBase(statsBase: Int)
|
||||
fun getStatsBase(): Int
|
||||
|
||||
fun useFlagAssistant(): Boolean
|
||||
fun useHapticFeedback(): Boolean
|
||||
fun useLargeAreas(): Boolean
|
||||
|
@ -83,6 +86,13 @@ class PreferencesRepository(
|
|||
putInt(PREFERENCE_CONTROL_STYLE, controlStyle.ordinal)
|
||||
}
|
||||
|
||||
override fun updateStatsBase(statsBase: Int) {
|
||||
putInt(PREFERENCE_STATS_BASE, statsBase)
|
||||
}
|
||||
|
||||
override fun getStatsBase(): Int =
|
||||
getInt(PREFERENCE_STATS_BASE, 0)
|
||||
|
||||
private fun migrateOldPreferences() {
|
||||
if (preferencesManager.contains(PREFERENCE_OLD_DOUBLE_CLICK)) {
|
||||
if (getBoolean(PREFERENCE_OLD_DOUBLE_CLICK, false)) {
|
||||
|
@ -105,5 +115,6 @@ class PreferencesRepository(
|
|||
private const val PREFERENCE_CUSTOM_GAME_HEIGHT = "preference_custom_game_height"
|
||||
private const val PREFERENCE_CUSTOM_GAME_MINES = "preference_custom_game_mines"
|
||||
private const val PREFERENCE_SOUND_EFFECTS = "preference_sound"
|
||||
private const val PREFERENCE_STATS_BASE = "preference_stats_base"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,8 @@
|
|||
<string name="no_network">No internet connection.</string>
|
||||
<string name="open_menu">Open Menu</string>
|
||||
<string name="close_menu">Close Menu</string>
|
||||
<string name="delete_all">Delete all</string>
|
||||
<string name="delete_all_message">Delete all events permanently.</string>
|
||||
<string name="all_mines_disabled">All mines were disabled.</string>
|
||||
<string name="desc_convered_area">Covered area</string>
|
||||
<string name="desc_marked_area">Marked area</string>
|
||||
|
|
|
@ -6,8 +6,8 @@ android {
|
|||
compileSdkVersion 30
|
||||
|
||||
defaultConfig {
|
||||
versionCode 702051 // MMmmPPv
|
||||
versionName '7.2.5'
|
||||
versionCode 702061 // MMmmPPv
|
||||
versionName '7.2.6'
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ android {
|
|||
compileSdkVersion 30
|
||||
|
||||
defaultConfig {
|
||||
versionCode 702051 // MMmmPPv
|
||||
versionName '7.2.5'
|
||||
versionCode 702061 // MMmmPPv
|
||||
versionName '7.2.6'
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
// versionCode and versionName must be hardcoded to support F-droid
|
||||
versionCode 702051
|
||||
versionName '7.2.5'
|
||||
versionCode 702061
|
||||
versionName '7.2.6'
|
||||
applicationId 'dev.lucasnlm.antimine'
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 30
|
||||
|
|
Loading…
Reference in a new issue