diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 64fad1e5..7b864994 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -139,11 +139,27 @@
+ android:theme="@style/AppTheme">
+
+
+ android:theme="@style/AppTheme">
+
+
+
+
+
+
openRateUsLink("Drawer")
R.id.share_now -> shareCurrentGame()
R.id.previous_games -> openSaveHistory()
+ R.id.stats -> openStats()
R.id.install_new -> installFromInstantApp()
else -> handled = false
}
@@ -373,6 +375,13 @@ class GameActivity : DaggerAppCompatActivity() {
}
}
+ private fun openStats() {
+ analyticsManager.sentEvent(Analytics.OpenStats())
+ Intent(this, StatsActivity::class.java).apply {
+ startActivity(this)
+ }
+ }
+
private fun showSettings() {
analyticsManager.sentEvent(Analytics.OpenSettings())
Intent(this, PreferencesActivity::class.java).apply {
diff --git a/app/src/main/java/dev/lucasnlm/antimine/about/AboutActivity.kt b/app/src/main/java/dev/lucasnlm/antimine/about/AboutActivity.kt
index 3640c1c7..b99236b3 100644
--- a/app/src/main/java/dev/lucasnlm/antimine/about/AboutActivity.kt
+++ b/app/src/main/java/dev/lucasnlm/antimine/about/AboutActivity.kt
@@ -4,7 +4,6 @@ import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
-import android.view.MenuItem
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.Observer
@@ -22,7 +21,7 @@ class AboutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_empty)
- bindToolbar()
+ setTitle(R.string.about)
aboutViewModel = ViewModelProviders.of(this).get(AboutViewModel::class.java)
@@ -60,23 +59,6 @@ class AboutActivity : AppCompatActivity() {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(SOURCE_CODE)))
}
- override fun onOptionsItemSelected(item: MenuItem): Boolean =
- when (item.itemId) {
- android.R.id.home -> {
- onBackPressed()
- true
- }
- else -> super.onOptionsItemSelected(item)
- }
-
- private fun bindToolbar() {
- supportActionBar?.apply {
- setTitle(R.string.about)
- setDisplayHomeAsUpEnabled(true)
- setHomeButtonEnabled(true)
- }
- }
-
companion object {
private const val SOURCE_CODE = "https://github.com/lucasnlm/antimine-android"
}
diff --git a/app/src/main/java/dev/lucasnlm/antimine/about/TextActivity.kt b/app/src/main/java/dev/lucasnlm/antimine/about/TextActivity.kt
index 6b2005ef..203f4ef0 100644
--- a/app/src/main/java/dev/lucasnlm/antimine/about/TextActivity.kt
+++ b/app/src/main/java/dev/lucasnlm/antimine/about/TextActivity.kt
@@ -2,7 +2,6 @@ package dev.lucasnlm.antimine.about
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
-import android.view.MenuItem
import android.view.View
import dev.lucasnlm.antimine.R
@@ -16,8 +15,8 @@ class TextActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ title = intent.getStringExtra(Constants.TEXT_TITLE)
setContentView(R.layout.activity_text)
- bindToolbar()
GlobalScope.launch {
withContext(Dispatchers.Main) {
@@ -41,23 +40,4 @@ class TextActivity : AppCompatActivity() {
}
}
}
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- var handled = false
-
- if (item.itemId == android.R.id.home) {
- onBackPressed()
- handled = true
- }
-
- return handled || super.onOptionsItemSelected(item)
- }
-
- private fun bindToolbar() {
- supportActionBar?.apply {
- title = intent.getStringExtra(Constants.TEXT_TITLE)
- setDisplayHomeAsUpEnabled(true)
- setHomeButtonEnabled(true)
- }
- }
}
diff --git a/app/src/main/java/dev/lucasnlm/antimine/di/ActivityModule.kt b/app/src/main/java/dev/lucasnlm/antimine/di/ActivityModule.kt
index 830267ae..ef39ba9d 100644
--- a/app/src/main/java/dev/lucasnlm/antimine/di/ActivityModule.kt
+++ b/app/src/main/java/dev/lucasnlm/antimine/di/ActivityModule.kt
@@ -6,6 +6,7 @@ import dagger.android.ContributesAndroidInjector
import dev.lucasnlm.antimine.TvGameActivity
import dev.lucasnlm.antimine.core.scope.ActivityScope
import dev.lucasnlm.antimine.history.views.HistoryFragment
+import dev.lucasnlm.antimine.stats.StatsActivity
@Module
interface ActivityModule {
@@ -17,6 +18,10 @@ interface ActivityModule {
@ContributesAndroidInjector
fun contributeHistoryFragmentInjector(): HistoryFragment
+ @ActivityScope
+ @ContributesAndroidInjector
+ fun contributeStatsActivityInjector(): StatsActivity
+
@ActivityScope
@ContributesAndroidInjector
fun contributeTvGameActivityInjector(): TvGameActivity
diff --git a/app/src/main/java/dev/lucasnlm/antimine/stats/StatsActivity.kt b/app/src/main/java/dev/lucasnlm/antimine/stats/StatsActivity.kt
new file mode 100644
index 00000000..b5afd585
--- /dev/null
+++ b/app/src/main/java/dev/lucasnlm/antimine/stats/StatsActivity.kt
@@ -0,0 +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)
+}
diff --git a/app/src/main/java/dev/lucasnlm/antimine/stats/model/StatsModel.kt b/app/src/main/java/dev/lucasnlm/antimine/stats/model/StatsModel.kt
new file mode 100644
index 00000000..6ec23f64
--- /dev/null
+++ b/app/src/main/java/dev/lucasnlm/antimine/stats/model/StatsModel.kt
@@ -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
+)
diff --git a/app/src/main/java/dev/lucasnlm/antimine/stats/viewmodel/StatsViewModel.kt b/app/src/main/java/dev/lucasnlm/antimine/stats/viewmodel/StatsViewModel.kt
new file mode 100644
index 00000000..dde6b28a
--- /dev/null
+++ b/app/src/main/java/dev/lucasnlm/antimine/stats/viewmodel/StatsViewModel.kt
@@ -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()
+
+ 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)
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/layout/activity_stats.xml b/app/src/main/res/layout/activity_stats.xml
new file mode 100644
index 00000000..5b79b190
--- /dev/null
+++ b/app/src/main/res/layout/activity_stats.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml
index 569c9360..9904ea16 100644
--- a/app/src/main/res/layout/fragment_history.xml
+++ b/app/src/main/res/layout/fragment_history.xml
@@ -8,6 +8,34 @@
android:fitsSystemWindows="false"
tools:context="dev.lucasnlm.antimine.history.views.HistoryFragment">
+
+
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@+id/divider" />
diff --git a/app/src/main/res/menu/nav_menu.xml b/app/src/main/res/menu/nav_menu.xml
index 2bd92f29..6a979a10 100644
--- a/app/src/main/res/menu/nav_menu.xml
+++ b/app/src/main/res/menu/nav_menu.xml
@@ -45,6 +45,11 @@
android:checkable="false"
android:title="@string/previous_games" />
+
+
,
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()
diff --git a/app/src/test/java/dev/lucasnlm/antimine/mocks/MockStatsRepository.kt b/app/src/test/java/dev/lucasnlm/antimine/mocks/MockStatsRepository.kt
new file mode 100644
index 00000000..814d0a0e
--- /dev/null
+++ b/app/src/test/java/dev/lucasnlm/antimine/mocks/MockStatsRepository.kt
@@ -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
+) : IStatsRepository {
+ override suspend fun getAllStats(): List = list
+
+ override suspend fun addStats(stats: Stats): Long? {
+ return null
+ }
+}
diff --git a/app/src/test/java/dev/lucasnlm/antimine/stats/viewmodel/StatsViewModelTest.kt b/app/src/test/java/dev/lucasnlm/antimine/stats/viewmodel/StatsViewModelTest.kt
new file mode 100644
index 00000000..11c39216
--- /dev/null
+++ b/app/src/test/java/dev/lucasnlm/antimine/stats/viewmodel/StatsViewModelTest.kt
@@ -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)
+ }
+}
diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/LevelFacade.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/LevelFacade.kt
index cdd09f4f..8a99c5d1 100644
--- a/common/src/main/java/dev/lucasnlm/antimine/common/level/LevelFacade.kt
+++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/LevelFacade.kt
@@ -2,6 +2,7 @@ package dev.lucasnlm.antimine.common.level
import dev.lucasnlm.antimine.common.level.database.models.Save
import dev.lucasnlm.antimine.common.level.database.models.SaveStatus
+import dev.lucasnlm.antimine.common.level.database.models.Stats
import dev.lucasnlm.antimine.common.level.models.Area
import dev.lucasnlm.antimine.common.level.models.Difficulty
import dev.lucasnlm.antimine.common.level.models.Mark
@@ -354,6 +355,27 @@ class LevelFacade {
)
}
+ fun getStats(duration: Long): Stats? {
+ val gameStatus: SaveStatus = when {
+ checkVictory() -> SaveStatus.VICTORY
+ hasAnyMineExploded() -> SaveStatus.DEFEAT
+ else -> SaveStatus.ON_GOING
+ }
+ return if (gameStatus == SaveStatus.ON_GOING) {
+ null
+ } else {
+ Stats(
+ 0,
+ duration,
+ mines.count(),
+ if (gameStatus == SaveStatus.VICTORY) 1 else 0,
+ minefield.width,
+ minefield.height,
+ mines.count { !it.isCovered }
+ )
+ }
+ }
+
fun setCurrentSaveId(id: Int) {
this.saveId = id.coerceAtLeast(0)
}
diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/database/AppDataBase.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/database/AppDataBase.kt
index 3be4a191..c6711788 100644
--- a/common/src/main/java/dev/lucasnlm/antimine/common/level/database/AppDataBase.kt
+++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/database/AppDataBase.kt
@@ -8,12 +8,15 @@ import dev.lucasnlm.antimine.common.level.database.converters.FieldConverter
import dev.lucasnlm.antimine.common.level.database.converters.MinefieldConverter
import dev.lucasnlm.antimine.common.level.database.converters.SaveStatusConverter
import dev.lucasnlm.antimine.common.level.database.dao.SaveDao
+import dev.lucasnlm.antimine.common.level.database.dao.StatsDao
import dev.lucasnlm.antimine.common.level.database.models.Save
+import dev.lucasnlm.antimine.common.level.database.models.Stats
@Database(
entities = [
- Save::class
- ], version = 2, exportSchema = false
+ Save::class,
+ Stats::class
+ ], version = 3, exportSchema = false
)
@TypeConverters(
FieldConverter::class,
@@ -23,4 +26,5 @@ import dev.lucasnlm.antimine.common.level.database.models.Save
)
abstract class AppDataBase : RoomDatabase() {
abstract fun saveDao(): SaveDao
+ abstract fun statsDao(): StatsDao
}
diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/database/dao/StatsDao.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/database/dao/StatsDao.kt
new file mode 100644
index 00000000..62414a7c
--- /dev/null
+++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/database/dao/StatsDao.kt
@@ -0,0 +1,16 @@
+package dev.lucasnlm.antimine.common.level.database.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import dev.lucasnlm.antimine.common.level.database.models.Stats
+
+@Dao
+interface StatsDao {
+ @Query("SELECT * FROM stats")
+ suspend fun getAll(): List
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertAll(vararg stats: Stats): Array
+}
diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/database/models/Stats.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/database/models/Stats.kt
new file mode 100644
index 00000000..49b8a378
--- /dev/null
+++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/database/models/Stats.kt
@@ -0,0 +1,29 @@
+package dev.lucasnlm.antimine.common.level.database.models
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+data class Stats(
+ @PrimaryKey(autoGenerate = true)
+ val uid: Int,
+
+ @ColumnInfo(name = "duration")
+ val duration: Long,
+
+ @ColumnInfo(name = "mines")
+ val mines: Int,
+
+ @ColumnInfo(name = "victory")
+ val victory: Int,
+
+ @ColumnInfo(name = "width")
+ val width: Int,
+
+ @ColumnInfo(name = "height")
+ val height: Int,
+
+ @ColumnInfo(name = "openArea")
+ val openArea: Int
+)
diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/di/LevelModule.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/di/LevelModule.kt
index 0d0dc70a..bceeb7ea 100644
--- a/common/src/main/java/dev/lucasnlm/antimine/common/level/di/LevelModule.kt
+++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/di/LevelModule.kt
@@ -12,8 +12,10 @@ import dev.lucasnlm.antimine.common.level.repository.DimensionRepository
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.repository.MinefieldRepository
import dev.lucasnlm.antimine.common.level.repository.SavesRepository
+import dev.lucasnlm.antimine.common.level.repository.StatsRepository
import dev.lucasnlm.antimine.common.level.utils.Clock
import dev.lucasnlm.antimine.common.level.utils.HapticFeedbackInteractor
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
@@ -35,10 +37,18 @@ open class LevelModule(
appDataBase.saveDao()
}
+ private val statsDao by lazy {
+ appDataBase.statsDao()
+ }
+
private val savesRepository by lazy {
SavesRepository(savesDao)
}
+ private val statsRepository by lazy {
+ StatsRepository(statsDao)
+ }
+
@Provides
open fun provideGameEventObserver(): MutableLiveData = MutableLiveData()
@@ -50,6 +60,7 @@ open class LevelModule(
application: Application,
eventObserver: MutableLiveData,
savesRepository: ISavesRepository,
+ statsRepository: IStatsRepository,
dimensionRepository: IDimensionRepository,
preferencesRepository: IPreferencesRepository,
hapticFeedbackInteractor: IHapticFeedbackInteractor,
@@ -60,6 +71,7 @@ open class LevelModule(
application,
eventObserver,
savesRepository,
+ statsRepository,
dimensionRepository,
preferencesRepository,
hapticFeedbackInteractor,
@@ -78,6 +90,9 @@ open class LevelModule(
@Provides
open fun provideSavesRepository(): ISavesRepository = savesRepository
+ @Provides
+ open fun provideStatsRepository(): IStatsRepository = statsRepository
+
@Provides
open fun provideMinefieldRepository(): IMinefieldRepository = MinefieldRepository()
diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/repository/StatsRepository.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/repository/StatsRepository.kt
new file mode 100644
index 00000000..e038c618
--- /dev/null
+++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/repository/StatsRepository.kt
@@ -0,0 +1,21 @@
+package dev.lucasnlm.antimine.common.level.repository
+
+import dev.lucasnlm.antimine.common.level.database.dao.StatsDao
+import dev.lucasnlm.antimine.common.level.database.models.Stats
+
+interface IStatsRepository {
+ suspend fun getAllStats(): List
+ suspend fun addStats(stats: Stats): Long?
+}
+
+class StatsRepository(
+ private val statsDao: StatsDao
+) : IStatsRepository {
+ override suspend fun getAllStats(): List {
+ return statsDao.getAll()
+ }
+
+ override suspend fun addStats(stats: Stats): Long? {
+ return statsDao.insertAll(stats).firstOrNull()
+ }
+}
diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt
index 09f87271..e33a986a 100644
--- a/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt
+++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt
@@ -13,6 +13,7 @@ import dev.lucasnlm.antimine.common.level.database.models.Save
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.core.analytics.AnalyticsManager
@@ -28,6 +29,7 @@ class GameViewModel(
val application: Application,
val eventObserver: MutableLiveData,
private val savesRepository: ISavesRepository,
+ private val statsRepository: IStatsRepository,
private val dimensionRepository: IDimensionRepository,
private val preferencesRepository: IPreferencesRepository,
private val hapticFeedbackInteractor: IHapticFeedbackInteractor,
@@ -146,6 +148,14 @@ class GameViewModel(
}
}
+ private suspend fun saveStats() {
+ if (initialized && levelFacade.hasMines) {
+ levelFacade.getStats(elapsedTimeSeconds.value ?: 0L)?.let {
+ statsRepository.addStats(it)
+ }
+ }
+ }
+
fun resumeGame() {
if (initialized && levelFacade.hasMines && !levelFacade.isGameOver()) {
eventObserver.postValue(Event.Resume)
@@ -303,6 +313,7 @@ class GameViewModel(
}
GlobalScope.launch {
+ saveStats()
saveGame()
}
}
@@ -321,6 +332,7 @@ class GameViewModel(
}
GlobalScope.launch {
+ saveStats()
saveGame()
}
}
diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModelFactory.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModelFactory.kt
index 694e3043..70e5f015 100644
--- a/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModelFactory.kt
+++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModelFactory.kt
@@ -8,6 +8,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.core.analytics.AnalyticsManager
@@ -18,6 +19,7 @@ class GameViewModelFactory @Inject constructor(
private val application: Application,
private val eventObserver: MutableLiveData,
private val savesRepository: ISavesRepository,
+ private val statsRepository: IStatsRepository,
private val dimensionRepository: IDimensionRepository,
private val preferencesRepository: IPreferencesRepository,
private val hapticFeedbackInteractor: IHapticFeedbackInteractor,
@@ -33,6 +35,7 @@ class GameViewModelFactory @Inject constructor(
application,
eventObserver,
savesRepository,
+ statsRepository,
dimensionRepository,
preferencesRepository,
hapticFeedbackInteractor,
diff --git a/common/src/main/java/dev/lucasnlm/antimine/core/analytics/models/Analytics.kt b/common/src/main/java/dev/lucasnlm/antimine/core/analytics/models/Analytics.kt
index e09285ba..aa155f1b 100644
--- a/common/src/main/java/dev/lucasnlm/antimine/core/analytics/models/Analytics.kt
+++ b/common/src/main/java/dev/lucasnlm/antimine/core/analytics/models/Analytics.kt
@@ -65,6 +65,8 @@ sealed class Analytics(
class OpenAbout : Analytics("Open About")
+ class OpenStats : Analytics("Open Stats")
+
class OpenSettings : Analytics("Open Settings")
class OpenSaveHistory : Analytics("Open Save History")
diff --git a/common/src/main/res/values-cs/strings.xml b/common/src/main/res/values-cs/strings.xml
index c4c57ffe..08496b46 100644
--- a/common/src/main/res/values-cs/strings.xml
+++ b/common/src/main/res/values-cs/strings.xml
@@ -3,6 +3,7 @@
Anti-Mine
Musíte vyčistit obdélníkovou desku obsahující skryté miny, aniž by kterákoliv z nich vybuchla.
Zbývající miny
+ Hry
Předchozí hry
Instalovat
Obtížnost
@@ -94,4 +95,8 @@
Pokud se vám tato hra líbí, dejte nám prosím zpětnou vazbu. Hodně nám to pomůže.
Ano ❤️️️
Ne
+ Open Areas
+ Total Time
+ Average Time
+ Performance
diff --git a/common/src/main/res/values-de/strings.xml b/common/src/main/res/values-de/strings.xml
index 929ce62e..701ab10e 100644
--- a/common/src/main/res/values-de/strings.xml
+++ b/common/src/main/res/values-de/strings.xml
@@ -3,6 +3,7 @@
Anti-Mine
Du musst eine rechteckige Tafel mit versteckten minen räumen, ohne dass eine davon explodiert.
Verbleibende Minen
+ Games
Vorherige Spiele
Installieren
Schwierigkeitsgrad
@@ -94,4 +95,8 @@
Wenn dir dieses Spiel gefällt, gib uns bitte eine Rückmeldung. Es wird uns sehr helfen.
Ja ❤️️️
Nein
+ Open Areas
+ Total Time
+ Average Time
+ Performance
diff --git a/common/src/main/res/values-el/strings.xml b/common/src/main/res/values-el/strings.xml
index fd64362c..eec2abe2 100644
--- a/common/src/main/res/values-el/strings.xml
+++ b/common/src/main/res/values-el/strings.xml
@@ -3,6 +3,7 @@
Antimine
Πρέπει να καθαρίσετε μια ορθογώνια πλακέτα που περιέχει κρυμμένες \"νάρκες\" χωρίς να πυροδοτήσετε καμία από αυτές.
Υπόλοιπες νάρκες
+ Games
Προηγούμενα Παιχνίδια
Εγκατάσταση
Δυσκολία
@@ -94,4 +95,8 @@
Αν σας αρέσει αυτό το παιχνίδι, παρακαλούμε δώστε μας τα σχόλιά σας. Θα μας βοηθήσει πολύ.
Ναι ❤️️️
Όχι
+ Open Areas
+ Total Time
+ Average Time
+ Performance
diff --git a/common/src/main/res/values-es/strings.xml b/common/src/main/res/values-es/strings.xml
index 50c9612f..c4f337a1 100644
--- a/common/src/main/res/values-es/strings.xml
+++ b/common/src/main/res/values-es/strings.xml
@@ -3,6 +3,7 @@
Anti-Mina
Usted tiene que limpiar un tablero cuadrado que contiene minas escondidas sin detornarlas.
Minas restantes
+ Games
Juegos anteriores
Instalar
Dificultad
@@ -78,7 +79,7 @@
Asistente de Juego
Área cubierta
Área marcada
- Area dudosa
+ Área dudosa
Área marcada incorrectamente
General
Vibrar al activar la explosión o la bandera
@@ -94,4 +95,8 @@
Si te gusta este juego, por favor danos un comentario. Nos ayudará mucho.
Sí ❤️️️
No
+ Open Areas
+ Total Time
+ Average Time
+ Performance
diff --git a/common/src/main/res/values-fr/strings.xml b/common/src/main/res/values-fr/strings.xml
index 47283c3b..a0d3751f 100644
--- a/common/src/main/res/values-fr/strings.xml
+++ b/common/src/main/res/values-fr/strings.xml
@@ -3,6 +3,7 @@
Anti-Mine
Vous devez vider un tableau rectangulaire contenant des mines cachées sans en détonner.
Mines restantes
+ Games
Parties précédentes
Installer
Difficulté
@@ -94,4 +95,8 @@
Si vous aimez ce jeu, n\'hésitez pas à jour donner un retour. Ça nous serait très utile.
Oui ❤️
Non
+ Open Areas
+ Total Time
+ Average Time
+ Performance
diff --git a/common/src/main/res/values-it/strings.xml b/common/src/main/res/values-it/strings.xml
index 850e8273..50ea3cac 100644
--- a/common/src/main/res/values-it/strings.xml
+++ b/common/src/main/res/values-it/strings.xml
@@ -3,6 +3,7 @@
Antimine
L\'obbiettivo del gioco è ripulire un campo rettangolare che contiene mine nascoste senza detonarne nessuna.
Mine rimanenti
+ Games
Previous Games
Installare
Difficoltà
@@ -94,4 +95,8 @@
Se ti piace questo gioco, per favore inviaci suggerimenti. Puoi aiutare a migliorarlo.
Sì ❤️️️
No
+ Open Areas
+ Total Time
+ Average Time
+ Performance
diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml
index ad51ece7..e05ee0f7 100644
--- a/common/src/main/res/values-pt/strings.xml
+++ b/common/src/main/res/values-pt/strings.xml
@@ -3,6 +3,7 @@
Anti-Mine
Encontre todas as minas escondidas no campo minado.
Minas Restantes
+ Games
Jogos Anteriores
Instalar
Dificuldade
@@ -94,4 +95,8 @@
Se você está gostando do jogo, por favor deixe um comentário! Isso nos ajuda muito.
Sim ❤️️️
Não
+ Open Areas
+ Total Time
+ Average Time
+ Performance
diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml
index 56199323..fdbfc807 100644
--- a/common/src/main/res/values-ru/strings.xml
+++ b/common/src/main/res/values-ru/strings.xml
@@ -3,6 +3,7 @@
Anti-Mine
Вам необходимо расчистить прямоугольную площадь со спрятанными минами, не взорвав ни одну из них.
Мин осталось
+ Games
Предыдущая партия
Установить
Сложность
@@ -94,4 +95,8 @@
Нравится игра? Оставьте отзыв, пожалуйста. Это нам очень поможет.
Да ❤️️️
Нет
+ Open Areas
+ Total Time
+ Average Time
+ Performance
diff --git a/common/src/main/res/values-tr/strings.xml b/common/src/main/res/values-tr/strings.xml
index 9236c6ae..7d3ec8d7 100644
--- a/common/src/main/res/values-tr/strings.xml
+++ b/common/src/main/res/values-tr/strings.xml
@@ -3,6 +3,7 @@
Anti-Mine
Hiçbirini patlatmadan gizli mayın içeren dikdörtgen bir tahtayı temizlemelisiniz.
Kalan mayınlar
+ Games
Önceki Oyun
Yükle
Zorluk
@@ -94,4 +95,8 @@
Bu oyunu beğendiyseniz, lütfen bize bir geri bildirim verin. Bize çok yardımcı olacak.
Evet ❤️️️
Hayır
+ Open Areas
+ Total Time
+ Average Time
+ Performance
diff --git a/common/src/main/res/values-uk/strings.xml b/common/src/main/res/values-uk/strings.xml
index aacaddb4..a64ba993 100644
--- a/common/src/main/res/values-uk/strings.xml
+++ b/common/src/main/res/values-uk/strings.xml
@@ -3,6 +3,7 @@
Сапер
Вам потрібно очистити поле від схованих мін, не детонуючи їх.
Залишилося мін
+ Games
Попередня гра
Встановити
Складність
@@ -94,4 +95,8 @@
Якщо вам подобається ця гра, то залиште нам свій відгук. Це нам дуже допоможе.
Так ❤️️️
Ні
+ Open Areas
+ Total Time
+ Average Time
+ Performance
diff --git a/common/src/main/res/values-vi/strings.xml b/common/src/main/res/values-vi/strings.xml
index 854671ed..33e5cc05 100644
--- a/common/src/main/res/values-vi/strings.xml
+++ b/common/src/main/res/values-vi/strings.xml
@@ -3,12 +3,15 @@
Dò mìn
Bạn phải mở tất cả các ô trên một bãi mìn mà không làm nổ cục mìn nào.
Số mìn còn lại
+ Games
+ Previous Games
Cài đặt
Độ khó
Tiêu chuẩn
Dễ
Trung bình
Khó
+ Mở
Mở menu
Đóng menu
Cài đặt
@@ -92,4 +95,8 @@
Nếu bạn thích trò chơi này, hãy gửi phản hồi cho chúng tôi. Nhận xét của bạn sẽ giúp chúng tôi rất nhiều.
Có ❤️️️
Không
+ Open Areas
+ Total Time
+ Average Time
+ Performance
diff --git a/common/src/main/res/values-zh/strings.xml b/common/src/main/res/values-zh/strings.xml
index d907a2e2..d4238311 100644
--- a/common/src/main/res/values-zh/strings.xml
+++ b/common/src/main/res/values-zh/strings.xml
@@ -3,6 +3,7 @@
反雷 - 扫雷
你需要清除一个隐藏着地雷的矩形面板,不能使任何地雷爆炸。
剩余地雷
+ Games
前一盘棋
安装
难度
@@ -94,4 +95,8 @@
如果你喜欢这个游戏,请给我们反馈。这将对我们有很多帮助。
是 ❤️️️
取消
+ Open Areas
+ Total Time
+ Average Time
+ Performance
diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml
index a3bacd6b..a6e4aff7 100644
--- a/common/src/main/res/values/strings.xml
+++ b/common/src/main/res/values/strings.xml
@@ -1,8 +1,9 @@
Antimine
- You have to clear a rectangular board containing hidden \"mines\" without detonating any of them.
+ You have to clear a rectangular board containing hidden mines without detonating any of them.
Remaining mines
+ Games
Previous Games
Install
Difficulty
@@ -94,4 +95,8 @@
If you like this game, please give us a feedback. It will help us a lot.
Yes ❤️️️
No
+ Open Areas
+ Total Time
+ Average Time
+ Performance
diff --git a/common/src/test/java/dev/lucasnlm/antimine/common/level/di/TestLevelModule.kt b/common/src/test/java/dev/lucasnlm/antimine/common/level/di/TestLevelModule.kt
index 70035fd9..ab9e8d08 100644
--- a/common/src/test/java/dev/lucasnlm/antimine/common/level/di/TestLevelModule.kt
+++ b/common/src/test/java/dev/lucasnlm/antimine/common/level/di/TestLevelModule.kt
@@ -6,12 +6,14 @@ import androidx.lifecycle.MutableLiveData
import dagger.Module
import dagger.Provides
import dev.lucasnlm.antimine.common.level.database.models.Save
+import dev.lucasnlm.antimine.common.level.database.models.Stats
import dev.lucasnlm.antimine.common.level.models.Difficulty
import dev.lucasnlm.antimine.common.level.models.Event
import dev.lucasnlm.antimine.common.level.models.Minefield
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.repository.MinefieldRepository
import dev.lucasnlm.antimine.common.level.repository.Size
import dev.lucasnlm.antimine.common.level.utils.Clock
@@ -35,6 +37,7 @@ class TestLevelModule(
application: Application,
eventObserver: MutableLiveData,
savesRepository: ISavesRepository,
+ statsRepository: IStatsRepository,
dimensionRepository: IDimensionRepository,
preferencesRepository: IPreferencesRepository,
hapticFeedbackInteractor: IHapticFeedbackInteractor,
@@ -45,6 +48,7 @@ class TestLevelModule(
application,
eventObserver,
savesRepository,
+ statsRepository,
dimensionRepository,
preferencesRepository,
hapticFeedbackInteractor,
@@ -91,6 +95,13 @@ class TestLevelModule(
override fun randomSeed(): Long = 200
}
+ @Provides
+ fun provideStatsRepository(): IStatsRepository = object : IStatsRepository {
+ override suspend fun getAllStats(): List = listOf()
+
+ override suspend fun addStats(stats: Stats): Long? = null
+ }
+
@Provides
fun provideHapticFeedbackInteractor(
application: Application,