Add Stats screen
This commit is contained in:
parent
78ea2d9f87
commit
a8ab8c4240
10 changed files with 244 additions and 8 deletions
|
@ -1,13 +1,46 @@
|
|||
package dev.lucasnlm.antimine.stats
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import dagger.android.support.DaggerAppCompatActivity
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.common.level.repository.IStatsRepository
|
||||
import dev.lucasnlm.antimine.stats.viewmodel.StatsViewModel
|
||||
import kotlinx.android.synthetic.main.activity_stats.*
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class StatsActivity : DaggerAppCompatActivity() {
|
||||
@Inject
|
||||
lateinit var statsRepository: IStatsRepository
|
||||
|
||||
private lateinit var viewModel: StatsViewModel
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_stats)
|
||||
setTitle(R.string.events)
|
||||
|
||||
viewModel = ViewModelProviders.of(this).get(StatsViewModel::class.java)
|
||||
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()
|
||||
})
|
||||
|
||||
GlobalScope.launch {
|
||||
viewModel.loadStats(statsRepository)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatPercentage(value: Double) =
|
||||
String.format("%.2f%%", value)
|
||||
|
||||
private fun formatTime(durationSecs: Long) =
|
||||
String.format("%02d:%02d:%02d", durationSecs / 3600, durationSecs % 3600 / 60, durationSecs % 60)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package dev.lucasnlm.antimine.stats.model
|
||||
|
||||
data class StatsModel(
|
||||
val totalGames: Int,
|
||||
val duration: Long,
|
||||
val averageDuration: Long,
|
||||
val mines: Int,
|
||||
val victory: Int,
|
||||
val openArea: Int
|
||||
)
|
|
@ -0,0 +1,41 @@
|
|||
package dev.lucasnlm.antimine.stats.viewmodel
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dev.lucasnlm.antimine.common.level.repository.IStatsRepository
|
||||
import dev.lucasnlm.antimine.stats.model.StatsModel
|
||||
|
||||
class StatsViewModel : ViewModel() {
|
||||
val statsObserver = MutableLiveData<StatsModel>()
|
||||
|
||||
suspend fun getStatsModel(statsRepository: IStatsRepository): StatsModel? {
|
||||
val stats = statsRepository.getAllStats()
|
||||
val statsCount = stats.count()
|
||||
|
||||
return if (statsCount > 0) {
|
||||
val result = stats.fold(
|
||||
StatsModel(statsCount, 0L, 0L, 0, 0, 0)
|
||||
) { acc, value ->
|
||||
StatsModel(
|
||||
acc.totalGames,
|
||||
acc.duration + value.duration,
|
||||
0,
|
||||
acc.mines + value.mines,
|
||||
acc.victory + value.victory,
|
||||
acc.openArea + value.openArea
|
||||
)
|
||||
}
|
||||
result.copy(averageDuration = result.duration / result.totalGames)
|
||||
} else {
|
||||
StatsModel(0, 0, 0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadStats(statsRepository: IStatsRepository) {
|
||||
getStatsModel(statsRepository)?.let {
|
||||
if (it.totalGames > 0) {
|
||||
statsObserver.postValue(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,32 +5,78 @@
|
|||
android:layout_height="match_parent"
|
||||
android:stretchColumns="1"
|
||||
android:fitsSystemWindows="true"
|
||||
android:divider="?android:listDivider"
|
||||
android:showDividers="middle"
|
||||
tools:context=".stats.StatsActivity">
|
||||
|
||||
<TableRow>
|
||||
<TextView
|
||||
android:padding="16dp"
|
||||
android:text="@string/games"
|
||||
android:textColor="@color/text_color" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/totalGames"
|
||||
android:gravity="end"
|
||||
android:padding="16dp"
|
||||
android:text="0"
|
||||
tools:ignore="HardcodedText" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TextView
|
||||
android:padding="16dp"
|
||||
android:text="@string/mines"
|
||||
android:textColor="@color/text_color" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/minesCount"
|
||||
android:gravity="end"
|
||||
android:padding="16dp"
|
||||
android:text="100" />
|
||||
android:text="-"
|
||||
tools:ignore="HardcodedText" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<TextView
|
||||
android:padding="16dp"
|
||||
android:text="@string/total_time"
|
||||
android:textColor="@color/text_color" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/totalTime"
|
||||
android:gravity="end"
|
||||
android:padding="16dp"
|
||||
android:text="100" />
|
||||
android:text="-"
|
||||
tools:ignore="HardcodedText" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TextView
|
||||
android:padding="16dp"
|
||||
android:text="@string/average_time"
|
||||
android:textColor="@color/text_color" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/averageTime"
|
||||
android:gravity="end"
|
||||
android:padding="16dp"
|
||||
android:text="-"
|
||||
tools:ignore="HardcodedText" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TextView
|
||||
android:padding="16dp"
|
||||
android:text="@string/open_areas"
|
||||
android:textColor="@color/text_color" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/openAreas"
|
||||
android:gravity="end"
|
||||
android:padding="16dp"
|
||||
android:text="-"
|
||||
tools:ignore="HardcodedText" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
@ -41,8 +87,10 @@
|
|||
android:textColor="@color/text_color" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/performance"
|
||||
android:gravity="end"
|
||||
android:padding="16dp"
|
||||
android:text="100" />
|
||||
android:text="-"
|
||||
tools:ignore="HardcodedText" />
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
|
|
|
@ -10,6 +10,7 @@ import dev.lucasnlm.antimine.common.level.models.Event
|
|||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.IStatsRepository
|
||||
import dev.lucasnlm.antimine.common.level.utils.Clock
|
||||
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
|
||||
import dev.lucasnlm.antimine.common.level.viewmodel.GameViewModelFactory
|
||||
|
@ -19,6 +20,7 @@ import dev.lucasnlm.antimine.mocks.MockDimensionRepository
|
|||
import dev.lucasnlm.antimine.mocks.MockHapticFeedbackInteractor
|
||||
import dev.lucasnlm.antimine.mocks.MockMinefieldRepository
|
||||
import dev.lucasnlm.antimine.mocks.MockSavesRepository
|
||||
import dev.lucasnlm.antimine.mocks.MockStatsRepository
|
||||
|
||||
@Module
|
||||
class TestLevelModule(
|
||||
|
@ -35,6 +37,7 @@ class TestLevelModule(
|
|||
application: Application,
|
||||
eventObserver: MutableLiveData<Event>,
|
||||
savesRepository: ISavesRepository,
|
||||
statsRepository: IStatsRepository,
|
||||
dimensionRepository: IDimensionRepository,
|
||||
preferencesRepository: IPreferencesRepository,
|
||||
hapticFeedbackInteractor: IHapticFeedbackInteractor,
|
||||
|
@ -45,6 +48,7 @@ class TestLevelModule(
|
|||
application,
|
||||
eventObserver,
|
||||
savesRepository,
|
||||
statsRepository,
|
||||
dimensionRepository,
|
||||
preferencesRepository,
|
||||
hapticFeedbackInteractor,
|
||||
|
@ -62,6 +66,9 @@ class TestLevelModule(
|
|||
@Provides
|
||||
override fun provideSavesRepository(): ISavesRepository = MockSavesRepository()
|
||||
|
||||
@Provides
|
||||
override fun provideStatsRepository(): IStatsRepository = MockStatsRepository(listOf())
|
||||
|
||||
@Provides
|
||||
override fun provideMinefieldRepository(): IMinefieldRepository = MockMinefieldRepository()
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package dev.lucasnlm.antimine.mocks
|
||||
|
||||
import dev.lucasnlm.antimine.common.level.database.models.Stats
|
||||
import dev.lucasnlm.antimine.common.level.repository.IStatsRepository
|
||||
|
||||
class MockStatsRepository(
|
||||
private val list: List<Stats>
|
||||
) : IStatsRepository {
|
||||
override suspend fun getAllStats(): List<Stats> = list
|
||||
|
||||
override suspend fun addStats(stats: Stats): Long? {
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package dev.lucasnlm.antimine.stats.viewmodel
|
||||
|
||||
import dev.lucasnlm.antimine.common.level.database.models.Stats
|
||||
import dev.lucasnlm.antimine.mocks.MockStatsRepository
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runBlockingTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class StatsViewModelTest {
|
||||
private val listOfStats = listOf(
|
||||
Stats(0, 1000, 10, 1, 10, 10, 90),
|
||||
Stats(1, 1200, 24, 0, 10, 10, 20)
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testStatsTotalGames() = runBlockingTest {
|
||||
val viewModel = StatsViewModel()
|
||||
val statsModel = viewModel.getStatsModel(MockStatsRepository(listOfStats))
|
||||
assertEquals(2, statsModel?.totalGames)
|
||||
|
||||
val emptyStatsModel = viewModel.getStatsModel(MockStatsRepository(listOf()))
|
||||
assertEquals(0, emptyStatsModel?.totalGames)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStatsDuration() = runBlockingTest {
|
||||
val viewModel = StatsViewModel()
|
||||
val statsModel = viewModel.getStatsModel(MockStatsRepository(listOfStats))
|
||||
assertEquals(2200L, statsModel?.duration)
|
||||
|
||||
val emptyStatsModel = viewModel.getStatsModel(MockStatsRepository(listOf()))
|
||||
assertEquals(0L, emptyStatsModel?.duration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStatsAverageDuration() = runBlockingTest {
|
||||
val viewModel = StatsViewModel()
|
||||
val statsModel = viewModel.getStatsModel(MockStatsRepository(listOfStats))
|
||||
assertEquals(1100L, statsModel?.averageDuration)
|
||||
|
||||
val emptyStatsModel = viewModel.getStatsModel(MockStatsRepository(listOf()))
|
||||
assertEquals(0L, emptyStatsModel?.averageDuration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStatsMines() = runBlockingTest {
|
||||
val viewModel = StatsViewModel()
|
||||
val statsModel = viewModel.getStatsModel(MockStatsRepository(listOfStats))
|
||||
assertEquals(34, statsModel?.mines)
|
||||
|
||||
val emptyStatsModel = viewModel.getStatsModel(MockStatsRepository(listOf()))
|
||||
assertEquals(0, emptyStatsModel?.mines)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVictory() = runBlockingTest {
|
||||
val viewModel = StatsViewModel()
|
||||
val statsModel = viewModel.getStatsModel(MockStatsRepository(listOfStats))
|
||||
assertEquals(1, statsModel?.victory)
|
||||
|
||||
val emptyStatsModel = viewModel.getStatsModel(MockStatsRepository(listOf()))
|
||||
assertEquals(0, emptyStatsModel?.victory)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOpenArea() = runBlockingTest {
|
||||
val viewModel = StatsViewModel()
|
||||
val statsModel = viewModel.getStatsModel(MockStatsRepository(listOfStats))
|
||||
assertEquals(110, statsModel?.openArea)
|
||||
|
||||
val emptyStatsModel = viewModel.getStatsModel(MockStatsRepository(listOf()))
|
||||
assertEquals(0, emptyStatsModel?.openArea)
|
||||
}
|
||||
}
|
|
@ -368,10 +368,11 @@ class LevelFacade {
|
|||
Stats(
|
||||
0,
|
||||
duration,
|
||||
mines.count().toLong(),
|
||||
mines.count(),
|
||||
if (gameStatus == SaveStatus.VICTORY) 1 else 0,
|
||||
minefield.width,
|
||||
minefield.height
|
||||
minefield.height,
|
||||
mines.count { !it.isCovered }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ data class Stats(
|
|||
val duration: Long,
|
||||
|
||||
@ColumnInfo(name = "mines")
|
||||
val mines: Long,
|
||||
val mines: Int,
|
||||
|
||||
@ColumnInfo(name = "victory")
|
||||
val victory: Int,
|
||||
|
@ -22,5 +22,8 @@ data class Stats(
|
|||
val width: Int,
|
||||
|
||||
@ColumnInfo(name = "height")
|
||||
val height: Int
|
||||
val height: Int,
|
||||
|
||||
@ColumnInfo(name = "openArea")
|
||||
val openArea: Int
|
||||
)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<string name="app_name">Antimine</string>
|
||||
<string name="app_description">You have to clear a rectangular board containing hidden \"mines\" without detonating any of them.</string>
|
||||
<string name="remaining_mines">Remaining mines</string>
|
||||
<string name="games">Games</string>
|
||||
<string name="previous_games">Previous Games</string>
|
||||
<string name="install">Install</string>
|
||||
<string name="minefield">Difficulty</string>
|
||||
|
@ -94,6 +95,8 @@
|
|||
<string name="rating_message">If you like this game, please give us a feedback. It will help us a lot.</string>
|
||||
<string name="rating_button">Yes ❤️️️</string>
|
||||
<string name="rating_button_no">No</string>
|
||||
<string name="open_areas">Open Areas</string>
|
||||
<string name="total_time">Total Time</string>
|
||||
<string name="average_time">Average Time</string>
|
||||
<string name="performance">Performance</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue