Merge pull request #159 from lucasnlm/add-support-screen
Add support screen
2
.gitignore
vendored
|
@ -12,3 +12,5 @@ app/local.properties
|
|||
app/release/
|
||||
app/standalone/
|
||||
app/google/
|
||||
app/google-services.json
|
||||
proprietary/google-services.json
|
|
@ -66,7 +66,7 @@ Where `ANDROID_JRE` is the Java runtime provided by Android Studio.
|
|||
- [Android SDK 30](https://developer.android.com/about/versions/11)
|
||||
- [AndroidX](https://developer.android.com/jetpack/androidx)
|
||||
- [Lifecycle](https://developer.android.com/topic/libraries/architecture/lifecycle)
|
||||
- [Dagger Hilt](https://dagger.dev/hilt/)
|
||||
- [Koin Kotlin](https://github.com/InsertKoinIO/koin)
|
||||
- [Room](https://developer.android.com/training/data-storage/room)
|
||||
- [Robolectric](http://robolectric.org/)
|
||||
- [Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'dagger.hilt.android.plugin'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
android {
|
||||
|
@ -9,9 +8,9 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
// versionCode and versionName must be hardcoded to support F-droid
|
||||
versionCode 800041
|
||||
versionName '8.0.4'
|
||||
minSdkVersion 16
|
||||
versionCode 800051
|
||||
versionName '8.0.5'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
multiDexEnabled true
|
||||
vectorDrawables.useSupportLibrary true
|
||||
|
@ -85,14 +84,11 @@ kapt {
|
|||
correctErrorTypes true
|
||||
}
|
||||
|
||||
hilt {
|
||||
enableTransformForLocalTests true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Dependencies must be hardcoded to support F-droid
|
||||
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation project(':external')
|
||||
implementation project(':common')
|
||||
|
||||
googleImplementation project(':proprietary')
|
||||
|
@ -119,15 +115,10 @@ dependencies {
|
|||
// Google
|
||||
implementation 'com.google.android.material:material:1.2.0'
|
||||
|
||||
// Dagger
|
||||
implementation 'com.google.dagger:hilt-android:2.28.1-alpha'
|
||||
kapt 'com.google.dagger:hilt-android-compiler:2.28.1-alpha'
|
||||
testImplementation 'com.google.dagger:hilt-android-testing:2.28.1-alpha'
|
||||
kaptTest 'com.google.dagger:hilt-android-compiler:2.28.1-alpha'
|
||||
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.28.1-alpha'
|
||||
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.28.1-alpha'
|
||||
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'
|
||||
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
|
||||
// Koin
|
||||
implementation 'org.koin:koin-android:2.1.6'
|
||||
implementation 'org.koin:koin-androidx-viewmodel:2.1.6'
|
||||
testImplementation 'org.koin:koin-test:2.1.6'
|
||||
|
||||
// Kotlin
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
|
||||
|
|
|
@ -106,7 +106,6 @@ class GameFlowTests {
|
|||
.perform(NavigationViewActions.navigateTo(R.id.control))
|
||||
|
||||
onView(withText(R.string.standard)).perform(click())
|
||||
onView(withText(R.string.flag_first)).perform(click())
|
||||
|
||||
onView(withText(R.string.ok)).perform(click())
|
||||
}
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
sdk=28
|
||||
application=dagger.hilt.android.testing.HiltTestApplication
|
|
@ -12,6 +12,4 @@ object DeepLink {
|
|||
const val EXPERT_PATH = "expert"
|
||||
const val STANDARD_PATH = "standard"
|
||||
const val CUSTOM_PATH = "custom"
|
||||
|
||||
const val CUSTOM_NEW_GAME = "antimine://new-game/custom"
|
||||
}
|
||||
|
|
|
@ -9,20 +9,20 @@ import android.os.Handler
|
|||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.viewModels
|
||||
import android.widget.FrameLayout
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.TooltipCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.HandlerCompat.postDelayed
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.doOnLayout
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.lucasnlm.antimine.about.AboutActivity
|
||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
import dev.lucasnlm.antimine.common.level.models.Event
|
||||
|
@ -36,13 +36,15 @@ import dev.lucasnlm.antimine.core.analytics.models.Analytics
|
|||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import dev.lucasnlm.antimine.custom.CustomLevelDialogFragment
|
||||
import dev.lucasnlm.antimine.history.HistoryActivity
|
||||
import dev.lucasnlm.antimine.instant.InstantAppManager
|
||||
import dev.lucasnlm.antimine.level.view.EndGameDialogFragment
|
||||
import dev.lucasnlm.antimine.level.view.LevelFragment
|
||||
import dev.lucasnlm.antimine.playgames.PlayGamesDialogFragment
|
||||
import dev.lucasnlm.antimine.preferences.PreferencesActivity
|
||||
import dev.lucasnlm.antimine.share.viewmodel.ShareViewModel
|
||||
import dev.lucasnlm.antimine.share.ShareManager
|
||||
import dev.lucasnlm.antimine.stats.StatsActivity
|
||||
import dev.lucasnlm.antimine.theme.ThemeActivity
|
||||
import dev.lucasnlm.external.IInstantAppManager
|
||||
import dev.lucasnlm.external.IPlayGamesManager
|
||||
import kotlinx.android.synthetic.main.activity_game.*
|
||||
import kotlinx.android.synthetic.main.activity_game.minesCount
|
||||
import kotlinx.android.synthetic.main.activity_game.timer
|
||||
|
@ -50,24 +52,23 @@ import kotlinx.android.synthetic.main.activity_tv_game.*
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
@AndroidEntryPoint
|
||||
class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.OnDismissListener {
|
||||
@Inject
|
||||
lateinit var preferencesRepository: IPreferencesRepository
|
||||
private val preferencesRepository: IPreferencesRepository by inject()
|
||||
|
||||
@Inject
|
||||
lateinit var analyticsManager: IAnalyticsManager
|
||||
private val analyticsManager: IAnalyticsManager by inject()
|
||||
|
||||
@Inject
|
||||
lateinit var instantAppManager: InstantAppManager
|
||||
private val instantAppManager: IInstantAppManager by inject()
|
||||
|
||||
@Inject
|
||||
lateinit var savesRepository: ISavesRepository
|
||||
private val savesRepository: ISavesRepository by inject()
|
||||
|
||||
val viewModel: GameViewModel by viewModels()
|
||||
private val shareViewModel: ShareViewModel by viewModels()
|
||||
private val playGamesManager: IPlayGamesManager by inject()
|
||||
|
||||
private val shareViewModel: ShareManager by inject()
|
||||
|
||||
val gameViewModel by viewModel<GameViewModel>()
|
||||
|
||||
override val noActionBar: Boolean = true
|
||||
|
||||
|
@ -90,9 +91,12 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
bindToolbar()
|
||||
bindDrawer()
|
||||
bindNavigationMenu()
|
||||
loadGameFragment()
|
||||
|
||||
if (instantAppManager.isEnabled()) {
|
||||
findViewById<FrameLayout>(R.id.levelContainer).doOnLayout {
|
||||
loadGameFragment()
|
||||
}
|
||||
|
||||
if (instantAppManager.isEnabled(applicationContext)) {
|
||||
bindInstantApp()
|
||||
savesRepository.setLimit(1)
|
||||
} else {
|
||||
|
@ -100,7 +104,7 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
}
|
||||
}
|
||||
|
||||
private fun bindViewModel() = viewModel.apply {
|
||||
private fun bindViewModel() = gameViewModel.apply {
|
||||
var lastEvent: Event? = null // TODO use distinctUntilChanged when available
|
||||
|
||||
eventObserver.observe(
|
||||
|
@ -117,7 +121,7 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
this@GameActivity,
|
||||
Observer {
|
||||
lifecycleScope.launch {
|
||||
viewModel.retryGame(currentSaveId.toInt())
|
||||
gameViewModel.retryGame(currentSaveId.toInt())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -179,9 +183,9 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
when {
|
||||
drawer.isDrawerOpen(GravityCompat.START) -> {
|
||||
drawer.closeDrawer(GravityCompat.START)
|
||||
viewModel.resumeGame()
|
||||
gameViewModel.resumeGame()
|
||||
}
|
||||
status == Status.Running && instantAppManager.isEnabled() -> showQuitConfirmation {
|
||||
status == Status.Running && instantAppManager.isEnabled(applicationContext) -> showQuitConfirmation {
|
||||
super.onBackPressed()
|
||||
}
|
||||
else -> super.onBackPressed()
|
||||
|
@ -194,13 +198,15 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
|
||||
if (!willReset) {
|
||||
if (status == Status.Running) {
|
||||
viewModel.run {
|
||||
gameViewModel.run {
|
||||
refreshUserPreferences()
|
||||
resumeGame()
|
||||
}
|
||||
|
||||
analyticsManager.sentEvent(Analytics.Resume)
|
||||
}
|
||||
|
||||
silentGooglePlayLogin()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,7 +214,7 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
super.onPause()
|
||||
|
||||
if (status == Status.Running) {
|
||||
viewModel.pauseGame()
|
||||
gameViewModel.pauseGame()
|
||||
}
|
||||
|
||||
if (isFinishing) {
|
||||
|
@ -242,12 +248,12 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
if (confirmResign) {
|
||||
newGameConfirmation {
|
||||
GlobalScope.launch {
|
||||
viewModel.startNewGame()
|
||||
gameViewModel.startNewGame()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GlobalScope.launch {
|
||||
viewModel.startNewGame()
|
||||
gameViewModel.startNewGame()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -284,13 +290,13 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
}
|
||||
|
||||
override fun onDrawerOpened(drawerView: View) {
|
||||
viewModel.pauseGame()
|
||||
gameViewModel.pauseGame()
|
||||
analyticsManager.sentEvent(Analytics.OpenDrawer)
|
||||
}
|
||||
|
||||
override fun onDrawerClosed(drawerView: View) {
|
||||
if (hasNoOtherFocusedDialog()) {
|
||||
viewModel.resumeGame()
|
||||
gameViewModel.resumeGame()
|
||||
}
|
||||
|
||||
analyticsManager.sentEvent(Analytics.CloseDrawer)
|
||||
|
@ -301,9 +307,9 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
}
|
||||
})
|
||||
|
||||
if (preferencesRepository.getBoolean(PREFERENCE_FIRST_USE, false)) {
|
||||
if (preferencesRepository.isFirstUse()) {
|
||||
openDrawer(GravityCompat.START)
|
||||
preferencesRepository.putBoolean(PREFERENCE_FIRST_USE, true)
|
||||
preferencesRepository.completeFirstUse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -327,6 +333,7 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
R.id.previous_games -> openSaveHistory()
|
||||
R.id.stats -> openStats()
|
||||
R.id.install_new -> installFromInstantApp()
|
||||
R.id.play_games -> googlePlay()
|
||||
else -> handled = false
|
||||
}
|
||||
|
||||
|
@ -337,19 +344,23 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
handled
|
||||
}
|
||||
|
||||
navigationView.menu.findItem(R.id.share_now).isVisible = instantAppManager.isNotEnabled()
|
||||
navigationView.menu.findItem(R.id.share_now).isVisible = !instantAppManager.isEnabled(applicationContext)
|
||||
|
||||
if (!playGamesManager.hasGooglePlayGames()) {
|
||||
navigationView.menu.removeGroup(R.id.play_games_group)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkUseCount() {
|
||||
val current = preferencesRepository.getInt(PREFERENCE_USE_COUNT, 0)
|
||||
val shouldRequestRating = preferencesRepository.getBoolean(PREFERENCE_REQUEST_RATING, true)
|
||||
val current = preferencesRepository.getUseCount()
|
||||
val shouldRequestRating = preferencesRepository.isRequestRatingEnabled()
|
||||
|
||||
if (current >= MIN_USAGES_TO_RATING && shouldRequestRating) {
|
||||
analyticsManager.sentEvent(Analytics.ShowRatingRequest(current))
|
||||
showRequestRating()
|
||||
}
|
||||
|
||||
preferencesRepository.putInt(PREFERENCE_USE_COUNT, current + 1)
|
||||
preferencesRepository.incrementUseCount()
|
||||
}
|
||||
|
||||
private fun onChangeDifficulty(difficulty: Difficulty) {
|
||||
|
@ -369,22 +380,22 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
}
|
||||
|
||||
private fun loadGameFragment() {
|
||||
val fragmentManager = supportFragmentManager
|
||||
supportFragmentManager.apply {
|
||||
popBackStack()
|
||||
|
||||
fragmentManager.popBackStack()
|
||||
findFragmentById(R.id.levelContainer)?.let { it ->
|
||||
beginTransaction().apply {
|
||||
remove(it)
|
||||
commitAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
fragmentManager.findFragmentById(R.id.levelContainer)?.let { it ->
|
||||
fragmentManager.beginTransaction().apply {
|
||||
remove(it)
|
||||
beginTransaction().apply {
|
||||
replace(R.id.levelContainer, LevelFragment())
|
||||
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
commitAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
fragmentManager.beginTransaction().apply {
|
||||
replace(R.id.levelContainer, LevelFragment())
|
||||
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
commitAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showRequestRating() {
|
||||
|
@ -397,7 +408,7 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
openRateUsLink("Dialog")
|
||||
}
|
||||
.setNegativeButton(R.string.rating_button_no) { _, _ ->
|
||||
preferencesRepository.putBoolean(PREFERENCE_REQUEST_RATING, false)
|
||||
preferencesRepository.disableRequestRating()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
@ -431,7 +442,7 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
}
|
||||
|
||||
private fun showControlDialog() {
|
||||
viewModel.pauseGame()
|
||||
gameViewModel.pauseGame()
|
||||
|
||||
if (supportFragmentManager.findFragmentByTag(CustomLevelDialogFragment.TAG) == null) {
|
||||
ControlDialogFragment().apply {
|
||||
|
@ -493,13 +504,13 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
}
|
||||
|
||||
private fun waitAndShowEndGameDialog(victory: Boolean, await: Boolean) {
|
||||
if (await && viewModel.explosionDelay() != 0L) {
|
||||
if (await && gameViewModel.explosionDelay() != 0L) {
|
||||
postDelayed(
|
||||
Handler(),
|
||||
{
|
||||
showEndGameDialog(victory)
|
||||
},
|
||||
null, (viewModel.explosionDelay() * 0.3).toLong()
|
||||
null, (gameViewModel.explosionDelay() * 0.3).toLong()
|
||||
)
|
||||
} else {
|
||||
showEndGameDialog(victory)
|
||||
|
@ -509,12 +520,12 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
private fun changeDifficulty(newDifficulty: Difficulty) {
|
||||
if (status == Status.PreGame) {
|
||||
GlobalScope.launch {
|
||||
viewModel.startNewGame(newDifficulty)
|
||||
gameViewModel.startNewGame(newDifficulty)
|
||||
}
|
||||
} else {
|
||||
newGameConfirmation {
|
||||
GlobalScope.launch {
|
||||
viewModel.startNewGame(newDifficulty)
|
||||
gameViewModel.startNewGame(newDifficulty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -532,7 +543,7 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
}
|
||||
Event.Resume, Event.Running -> {
|
||||
status = Status.Running
|
||||
viewModel.runClock()
|
||||
gameViewModel.runClock()
|
||||
refreshNewGameButton()
|
||||
keepScreenOn(true)
|
||||
}
|
||||
|
@ -543,9 +554,9 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
totalArea
|
||||
)
|
||||
status = Status.Over(currentTime, score)
|
||||
viewModel.stopClock()
|
||||
viewModel.revealAllEmptyAreas()
|
||||
viewModel.victory()
|
||||
gameViewModel.stopClock()
|
||||
gameViewModel.revealAllEmptyAreas()
|
||||
gameViewModel.victory()
|
||||
refreshNewGameButton()
|
||||
keepScreenOn(false)
|
||||
waitAndShowEndGameDialog(
|
||||
|
@ -563,10 +574,10 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
status = Status.Over(currentTime, score)
|
||||
refreshNewGameButton()
|
||||
keepScreenOn(false)
|
||||
viewModel.stopClock()
|
||||
gameViewModel.stopClock()
|
||||
|
||||
GlobalScope.launch(context = Dispatchers.Main) {
|
||||
viewModel.gameOver(isResuming)
|
||||
gameViewModel.gameOver(isResuming)
|
||||
waitAndShowEndGameDialog(
|
||||
victory = false,
|
||||
await = true
|
||||
|
@ -590,10 +601,10 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
}
|
||||
|
||||
private fun shareCurrentGame() {
|
||||
val levelSetup = viewModel.levelSetup.value
|
||||
val field = viewModel.field.value
|
||||
GlobalScope.launch {
|
||||
shareViewModel.share(levelSetup, field)
|
||||
val levelSetup = gameViewModel.levelSetup.value
|
||||
val field = gameViewModel.field.value
|
||||
lifecycleScope.launch {
|
||||
shareViewModel.shareField(levelSetup, field)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -625,7 +636,7 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
}
|
||||
|
||||
analyticsManager.sentEvent(Analytics.TapRatingRequest(from))
|
||||
preferencesRepository.putBoolean(PREFERENCE_REQUEST_RATING, false)
|
||||
preferencesRepository.disableRequestRating()
|
||||
}
|
||||
|
||||
private fun keepScreenOn(enabled: Boolean) {
|
||||
|
@ -643,19 +654,42 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface?) {
|
||||
viewModel.run {
|
||||
gameViewModel.run {
|
||||
refreshUserPreferences()
|
||||
resumeGame()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PREFERENCE_FIRST_USE = "preference_first_use"
|
||||
const val PREFERENCE_USE_COUNT = "preference_use_count"
|
||||
const val PREFERENCE_REQUEST_RATING = "preference_request_rating"
|
||||
private fun silentGooglePlayLogin() {
|
||||
if (playGamesManager.hasGooglePlayGames()) {
|
||||
playGamesManager.silentLogin(this)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
|
||||
private fun googlePlay() {
|
||||
if (playGamesManager.isLogged()) {
|
||||
PlayGamesDialogFragment().show(supportFragmentManager, PlayGamesDialogFragment.TAG)
|
||||
} else {
|
||||
playGamesManager.getLoginIntent()?.let {
|
||||
startActivityForResult(it, GOOGLE_PLAY_REQUEST_CODE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
if (requestCode == GOOGLE_PLAY_REQUEST_CODE) {
|
||||
playGamesManager.handleLoginResult(data)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val IA_REFERRER = "InstallApiActivity"
|
||||
const val IA_REQUEST_CODE = 5
|
||||
const val GOOGLE_PLAY_REQUEST_CODE = 6
|
||||
|
||||
const val MIN_USAGES_TO_RATING = 4
|
||||
}
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
package dev.lucasnlm.antimine
|
||||
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import dev.lucasnlm.antimine.common.level.di.LevelModule
|
||||
import dev.lucasnlm.antimine.core.analytics.IAnalyticsManager
|
||||
import dev.lucasnlm.antimine.core.analytics.models.Analytics
|
||||
import javax.inject.Inject
|
||||
import dev.lucasnlm.antimine.core.di.CommonModule
|
||||
import dev.lucasnlm.antimine.di.AppModule
|
||||
import dev.lucasnlm.antimine.di.ViewModelModule
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.context.startKoin
|
||||
|
||||
@HiltAndroidApp
|
||||
open class MainApplication : MultiDexApplication() {
|
||||
@Inject
|
||||
lateinit var analyticsManager: IAnalyticsManager
|
||||
private val analyticsManager: IAnalyticsManager by inject()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
startKoin {
|
||||
androidContext(applicationContext)
|
||||
modules(AppModule, CommonModule, LevelModule, ViewModelModule)
|
||||
}
|
||||
|
||||
analyticsManager.apply {
|
||||
setup(applicationContext, mapOf())
|
||||
sentEvent(Analytics.Open)
|
||||
|
|
|
@ -5,14 +5,11 @@ import androidx.annotation.LayoutRes
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import dev.lucasnlm.antimine.core.themes.model.AppTheme
|
||||
import dev.lucasnlm.antimine.core.themes.repository.IThemeRepository
|
||||
import javax.inject.Inject
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
open class ThematicActivity : AppCompatActivity {
|
||||
constructor() : super()
|
||||
constructor(@LayoutRes contentLayoutId: Int) : super(contentLayoutId)
|
||||
open class ThematicActivity(@LayoutRes contentLayoutId: Int) : AppCompatActivity(contentLayoutId) {
|
||||
|
||||
@Inject
|
||||
lateinit var themeRepository: IThemeRepository
|
||||
private val themeRepository: IThemeRepository by inject()
|
||||
|
||||
protected open val noActionBar: Boolean = false
|
||||
|
||||
|
|
|
@ -5,14 +5,12 @@ import android.os.Bundle
|
|||
import android.os.Handler
|
||||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.os.HandlerCompat
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.preference.PreferenceManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.lucasnlm.antimine.about.AboutActivity
|
||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
import dev.lucasnlm.antimine.common.level.models.Event
|
||||
|
@ -26,10 +24,10 @@ import kotlinx.android.synthetic.main.activity_tv_game.*
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
@AndroidEntryPoint
|
||||
class TvGameActivity : AppCompatActivity() {
|
||||
private val viewModel: GameViewModel by viewModels()
|
||||
private val gameViewModel by viewModel<GameViewModel>()
|
||||
|
||||
private var status: Status = Status.PreGame
|
||||
private var totalMines: Int = 0
|
||||
|
@ -49,7 +47,7 @@ class TvGameActivity : AppCompatActivity() {
|
|||
loadGameFragment()
|
||||
}
|
||||
|
||||
private fun bindViewModel() = viewModel.apply {
|
||||
private fun bindViewModel() = gameViewModel.apply {
|
||||
eventObserver.observe(
|
||||
this@TvGameActivity,
|
||||
Observer {
|
||||
|
@ -99,7 +97,7 @@ class TvGameActivity : AppCompatActivity() {
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (status == Status.Running) {
|
||||
viewModel.resumeGame()
|
||||
gameViewModel.resumeGame()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +105,7 @@ class TvGameActivity : AppCompatActivity() {
|
|||
super.onPause()
|
||||
|
||||
if (status == Status.Running) {
|
||||
viewModel.pauseGame()
|
||||
gameViewModel.pauseGame()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,7 +172,7 @@ class TvGameActivity : AppCompatActivity() {
|
|||
setCancelable(false)
|
||||
setPositiveButton(R.string.new_game) { _, _ ->
|
||||
GlobalScope.launch {
|
||||
viewModel.startNewGame()
|
||||
gameViewModel.startNewGame()
|
||||
}
|
||||
}
|
||||
setNegativeButton(R.string.cancel, null)
|
||||
|
@ -193,7 +191,7 @@ class TvGameActivity : AppCompatActivity() {
|
|||
setMessage(R.string.new_game_request)
|
||||
setPositiveButton(R.string.yes) { _, _ ->
|
||||
GlobalScope.launch {
|
||||
viewModel.startNewGame()
|
||||
gameViewModel.startNewGame()
|
||||
}
|
||||
}
|
||||
setNegativeButton(R.string.cancel, null)
|
||||
|
@ -217,7 +215,7 @@ class TvGameActivity : AppCompatActivity() {
|
|||
setMessage(R.string.new_game_request)
|
||||
setPositiveButton(R.string.yes) { _, _ ->
|
||||
GlobalScope.launch {
|
||||
viewModel.startNewGame()
|
||||
gameViewModel.startNewGame()
|
||||
}
|
||||
}
|
||||
setNegativeButton(R.string.cancel, null)
|
||||
|
@ -231,12 +229,12 @@ class TvGameActivity : AppCompatActivity() {
|
|||
private fun changeDifficulty(newDifficulty: Difficulty) {
|
||||
if (status == Status.PreGame) {
|
||||
GlobalScope.launch {
|
||||
viewModel.startNewGame(newDifficulty)
|
||||
gameViewModel.startNewGame(newDifficulty)
|
||||
}
|
||||
} else {
|
||||
newGameConfirmation {
|
||||
GlobalScope.launch {
|
||||
viewModel.startNewGame(newDifficulty)
|
||||
gameViewModel.startNewGame(newDifficulty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -253,7 +251,7 @@ class TvGameActivity : AppCompatActivity() {
|
|||
}
|
||||
Event.Resume, Event.Running -> {
|
||||
status = Status.Running
|
||||
viewModel.runClock()
|
||||
gameViewModel.runClock()
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
Event.Victory -> {
|
||||
|
@ -263,8 +261,8 @@ class TvGameActivity : AppCompatActivity() {
|
|||
totalArea
|
||||
)
|
||||
status = Status.Over(currentTime, score)
|
||||
viewModel.stopClock()
|
||||
viewModel.revealAllEmptyAreas()
|
||||
gameViewModel.stopClock()
|
||||
gameViewModel.revealAllEmptyAreas()
|
||||
invalidateOptionsMenu()
|
||||
showVictory()
|
||||
}
|
||||
|
@ -276,10 +274,10 @@ class TvGameActivity : AppCompatActivity() {
|
|||
)
|
||||
status = Status.Over(currentTime, score)
|
||||
invalidateOptionsMenu()
|
||||
viewModel.stopClock()
|
||||
gameViewModel.stopClock()
|
||||
|
||||
GlobalScope.launch(context = Dispatchers.Main) {
|
||||
viewModel.gameOver(false)
|
||||
gameViewModel.gameOver(false)
|
||||
waitAndShowGameOverConfirmNewGame()
|
||||
}
|
||||
}
|
||||
|
@ -291,7 +289,7 @@ class TvGameActivity : AppCompatActivity() {
|
|||
)
|
||||
status = Status.Over(currentTime, score)
|
||||
invalidateOptionsMenu()
|
||||
viewModel.stopClock()
|
||||
gameViewModel.stopClock()
|
||||
|
||||
waitAndShowConfirmNewGame()
|
||||
}
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
package dev.lucasnlm.antimine.about
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.viewModels
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.ThematicActivity
|
||||
import dev.lucasnlm.antimine.about.viewmodel.AboutEvent
|
||||
import dev.lucasnlm.antimine.about.viewmodel.AboutViewModel
|
||||
import dev.lucasnlm.antimine.about.views.AboutInfoFragment
|
||||
import dev.lucasnlm.antimine.about.views.info.AboutInfoFragment
|
||||
import dev.lucasnlm.antimine.about.views.licenses.LicensesFragment
|
||||
import dev.lucasnlm.antimine.about.views.translators.TranslatorsFragment
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AboutActivity : ThematicActivity(R.layout.activity_empty) {
|
||||
private val aboutViewModel: AboutViewModel by viewModels()
|
||||
private val aboutViewModel: AboutViewModel by viewModel()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
|
@ -3,13 +3,11 @@ package dev.lucasnlm.antimine.about.viewmodel
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.hilt.lifecycle.ViewModelInject
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.core.viewmodel.IntentViewModel
|
||||
|
||||
class AboutViewModel @ViewModelInject constructor(
|
||||
@ApplicationContext private val context: Context
|
||||
class AboutViewModel(
|
||||
private val context: Context
|
||||
) : IntentViewModel<AboutEvent, AboutState>() {
|
||||
|
||||
override fun onEvent(event: AboutEvent) {
|
||||
|
@ -47,10 +45,11 @@ class AboutViewModel @ViewModelInject constructor(
|
|||
|
||||
private fun getLicensesList() = mapOf(
|
||||
"Android SDK License" to R.raw.android_sdk,
|
||||
"Material Design Icons" to R.raw.apache2,
|
||||
"Dagger Hilt" to R.raw.apache2,
|
||||
"Material Design" to R.raw.apache2,
|
||||
"Koin" to R.raw.apache2,
|
||||
"Moshi" to R.raw.apache2,
|
||||
"Mockito" to R.raw.mockito,
|
||||
"Noto Emoji" to R.raw.apache2,
|
||||
"Sounds" to R.raw.sounds
|
||||
).map {
|
||||
License(it.key, it.value)
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
package dev.lucasnlm.antimine.about.views
|
||||
package dev.lucasnlm.antimine.about.views.info
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import dev.lucasnlm.antimine.BuildConfig
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.about.viewmodel.AboutEvent
|
||||
import dev.lucasnlm.antimine.about.viewmodel.AboutViewModel
|
||||
import kotlinx.android.synthetic.main.fragment_about_info.*
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
|
||||
class AboutInfoFragment : Fragment(R.layout.fragment_about_info) {
|
||||
private val aboutViewModel: AboutViewModel by activityViewModels()
|
||||
private val aboutViewModel: AboutViewModel by sharedViewModel()
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
|
@ -3,7 +3,6 @@ package dev.lucasnlm.antimine.about.views.licenses
|
|||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
@ -11,9 +10,10 @@ import dev.lucasnlm.antimine.R
|
|||
import dev.lucasnlm.antimine.about.viewmodel.AboutViewModel
|
||||
import kotlinx.android.synthetic.main.fragment_licenses.*
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
|
||||
class LicensesFragment : Fragment(R.layout.fragment_licenses) {
|
||||
private val aboutViewModel: AboutViewModel by activityViewModels()
|
||||
private val aboutViewModel: AboutViewModel by sharedViewModel()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
|
|
@ -4,7 +4,6 @@ import android.os.Bundle
|
|||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
@ -14,9 +13,10 @@ import dev.lucasnlm.antimine.about.viewmodel.AboutViewModel
|
|||
import kotlinx.android.synthetic.main.fragment_translators.*
|
||||
import kotlinx.android.synthetic.main.view_translator.view.*
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
|
||||
internal class TranslatorsFragment : Fragment(R.layout.fragment_translators) {
|
||||
private val aboutViewModel: AboutViewModel by activityViewModels()
|
||||
private val aboutViewModel: AboutViewModel by sharedViewModel()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
|
|
@ -8,23 +8,21 @@ import android.view.ViewGroup
|
|||
import android.widget.BaseAdapter
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatDialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.control.view.ControlItemView
|
||||
import dev.lucasnlm.antimine.control.viewmodel.ControlEvent
|
||||
import dev.lucasnlm.antimine.control.viewmodel.ControlViewModel
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ControlDialogFragment : AppCompatDialogFragment() {
|
||||
private val controlViewModel by activityViewModels<ControlViewModel>()
|
||||
private val controlViewModel by viewModel<ControlViewModel>()
|
||||
private val adapter by lazy { ControlListAdapter(controlViewModel) }
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val currentControl = controlViewModel.singleState().selectedId
|
||||
val state = controlViewModel.singleState()
|
||||
return AlertDialog.Builder(requireContext()).apply {
|
||||
setTitle(R.string.control)
|
||||
setSingleChoiceItems(adapter, currentControl, null)
|
||||
setSingleChoiceItems(adapter, state.selectedIndex, null)
|
||||
setPositiveButton(R.string.ok, null)
|
||||
}.create()
|
||||
}
|
||||
|
@ -41,8 +39,6 @@ class ControlDialogFragment : AppCompatDialogFragment() {
|
|||
) : BaseAdapter() {
|
||||
private val controlList = controlViewModel.singleState().gameControls
|
||||
|
||||
fun getSelectedId() = controlViewModel.singleState().selectedId
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
val view = if (convertView == null) {
|
||||
ControlItemView(parent!!.context)
|
||||
|
@ -50,12 +46,12 @@ class ControlDialogFragment : AppCompatDialogFragment() {
|
|||
(convertView as ControlItemView)
|
||||
}
|
||||
|
||||
val selectedId = getSelectedId()
|
||||
val selected = controlViewModel.singleState().selected
|
||||
|
||||
return view.apply {
|
||||
val controlModel = controlList[position]
|
||||
bind(controlModel)
|
||||
setRadio(selectedId == controlModel.controlStyle.ordinal)
|
||||
setRadio(selected == controlModel.controlStyle)
|
||||
setOnClickListener {
|
||||
controlViewModel.sendEvent(ControlEvent.SelectControlStyle(controlModel.controlStyle))
|
||||
notifyDataSetChanged()
|
||||
|
|
|
@ -6,7 +6,6 @@ import dev.lucasnlm.antimine.core.control.ControlStyle
|
|||
data class ControlDetails(
|
||||
val id: Long,
|
||||
val controlStyle: ControlStyle,
|
||||
@StringRes val titleId: Int,
|
||||
@StringRes val firstActionId: Int,
|
||||
@StringRes val firstActionResponseId: Int,
|
||||
@StringRes val secondActionId: Int,
|
||||
|
|
|
@ -17,7 +17,6 @@ class ControlItemView : FrameLayout {
|
|||
|
||||
private val radio: AppCompatRadioButton
|
||||
private val root: View
|
||||
private val title: TextView
|
||||
private val firstAction: TextView
|
||||
private val firstActionResponse: TextView
|
||||
private val secondAction: TextView
|
||||
|
@ -30,7 +29,6 @@ class ControlItemView : FrameLayout {
|
|||
|
||||
radio = findViewById(R.id.radio)
|
||||
root = findViewById(R.id.root)
|
||||
title = findViewById(R.id.title)
|
||||
firstAction = findViewById(R.id.firstAction)
|
||||
firstActionResponse = findViewById(R.id.firstActionResponse)
|
||||
secondAction = findViewById(R.id.secondAction)
|
||||
|
@ -38,7 +36,6 @@ class ControlItemView : FrameLayout {
|
|||
}
|
||||
|
||||
fun bind(controlDetails: ControlDetails) {
|
||||
title.text = context.getString(controlDetails.titleId)
|
||||
firstAction.text = context.getString(controlDetails.firstActionId)
|
||||
firstActionResponse.text = context.getString(controlDetails.firstActionResponseId)
|
||||
secondAction.text = context.getString(controlDetails.secondActionId)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package dev.lucasnlm.antimine.control.viewmodel
|
||||
|
||||
import dev.lucasnlm.antimine.control.models.ControlDetails
|
||||
import dev.lucasnlm.antimine.core.control.ControlStyle
|
||||
|
||||
data class ControlState(
|
||||
val selectedId: Int,
|
||||
val selectedIndex: Int,
|
||||
val selected: ControlStyle,
|
||||
val gameControls: List<ControlDetails>
|
||||
)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package dev.lucasnlm.antimine.control.viewmodel
|
||||
|
||||
import androidx.hilt.lifecycle.ViewModelInject
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.control.models.ControlDetails
|
||||
import dev.lucasnlm.antimine.core.control.ControlStyle
|
||||
|
@ -8,7 +7,7 @@ import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
|||
import dev.lucasnlm.antimine.core.viewmodel.IntentViewModel
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
class ControlViewModel @ViewModelInject constructor(
|
||||
class ControlViewModel(
|
||||
private val preferencesRepository: IPreferencesRepository
|
||||
) : IntentViewModel<ControlEvent, ControlState>() {
|
||||
|
||||
|
@ -16,7 +15,6 @@ class ControlViewModel @ViewModelInject constructor(
|
|||
ControlDetails(
|
||||
id = 0L,
|
||||
controlStyle = ControlStyle.Standard,
|
||||
titleId = R.string.standard,
|
||||
firstActionId = R.string.single_click,
|
||||
firstActionResponseId = R.string.open_tile,
|
||||
secondActionId = R.string.long_press,
|
||||
|
@ -25,7 +23,6 @@ class ControlViewModel @ViewModelInject constructor(
|
|||
ControlDetails(
|
||||
id = 1L,
|
||||
controlStyle = ControlStyle.FastFlag,
|
||||
titleId = R.string.flag_first,
|
||||
firstActionId = R.string.single_click,
|
||||
firstActionResponseId = R.string.flag_tile,
|
||||
secondActionId = R.string.long_press,
|
||||
|
@ -34,29 +31,42 @@ class ControlViewModel @ViewModelInject constructor(
|
|||
ControlDetails(
|
||||
id = 2L,
|
||||
controlStyle = ControlStyle.DoubleClick,
|
||||
titleId = R.string.double_click,
|
||||
firstActionId = R.string.single_click,
|
||||
firstActionResponseId = R.string.flag_tile,
|
||||
secondActionId = R.string.double_click,
|
||||
secondActionResponseId = R.string.open_tile
|
||||
),
|
||||
ControlDetails(
|
||||
id = 3L,
|
||||
controlStyle = ControlStyle.DoubleClickInverted,
|
||||
firstActionId = R.string.single_click,
|
||||
firstActionResponseId = R.string.open_tile,
|
||||
secondActionId = R.string.double_click,
|
||||
secondActionResponseId = R.string.flag_tile
|
||||
)
|
||||
)
|
||||
|
||||
override fun initialState(): ControlState =
|
||||
ControlState(
|
||||
selectedId = gameControlOptions.firstOrNull {
|
||||
it.controlStyle == preferencesRepository.controlStyle()
|
||||
}?.id?.toInt() ?: 0,
|
||||
override fun initialState(): ControlState {
|
||||
val controlDetails = gameControlOptions.firstOrNull {
|
||||
it.controlStyle == preferencesRepository.controlStyle()
|
||||
}
|
||||
return ControlState(
|
||||
selectedIndex = controlDetails?.id?.toInt() ?: 0,
|
||||
selected = controlDetails?.controlStyle ?: ControlStyle.Standard,
|
||||
gameControls = gameControlOptions
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun mapEventToState(event: ControlEvent) = flow {
|
||||
if (event is ControlEvent.SelectControlStyle) {
|
||||
val controlStyle = event.controlStyle
|
||||
preferencesRepository.useControlStyle(controlStyle)
|
||||
|
||||
val selected = state.gameControls.first { it.controlStyle == event.controlStyle }
|
||||
|
||||
val newState = state.copy(
|
||||
selectedId = state.gameControls.first { it.controlStyle == event.controlStyle }.id.toInt()
|
||||
selectedIndex = selected.id.toInt(),
|
||||
selected = selected.controlStyle
|
||||
)
|
||||
|
||||
emit(newState)
|
||||
|
|
|
@ -1,60 +1,67 @@
|
|||
package dev.lucasnlm.antimine.custom
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatDialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
import dev.lucasnlm.antimine.common.level.viewmodel.GameViewModel
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import dev.lucasnlm.antimine.custom.viewmodel.CreateGameViewModel
|
||||
import dev.lucasnlm.antimine.custom.viewmodel.CustomEvent
|
||||
import javax.inject.Inject
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
@AndroidEntryPoint
|
||||
class CustomLevelDialogFragment : AppCompatDialogFragment() {
|
||||
@Inject
|
||||
lateinit var preferencesRepository: IPreferencesRepository
|
||||
private val gameViewModel by sharedViewModel<GameViewModel>()
|
||||
private val createGameViewModel by viewModel<CreateGameViewModel>()
|
||||
|
||||
private val viewModel by activityViewModels<GameViewModel>()
|
||||
private val createGameViewModel by activityViewModels<CreateGameViewModel>()
|
||||
private lateinit var mapWidth: TextView
|
||||
private lateinit var mapHeight: TextView
|
||||
private lateinit var mapMines: TextView
|
||||
|
||||
private fun getSelectedMinefield(): Minefield {
|
||||
val mapWidth: TextView? = dialog?.findViewById(R.id.map_width)
|
||||
val mapHeight: TextView? = dialog?.findViewById(R.id.map_height)
|
||||
val mapMines: TextView? = dialog?.findViewById(R.id.map_mines)
|
||||
|
||||
val width = filterInput(
|
||||
mapWidth?.text.toString(),
|
||||
MIN_WIDTH
|
||||
).coerceAtMost(MAX_WIDTH)
|
||||
val height = filterInput(
|
||||
mapHeight?.text.toString(),
|
||||
MIN_HEIGHT
|
||||
).coerceAtMost(MAX_HEIGHT)
|
||||
val mines = filterInput(
|
||||
mapMines?.text.toString(),
|
||||
MIN_MINES
|
||||
).coerceAtMost(width * height - 1)
|
||||
val width = filterInput(mapWidth.text.toString(), MIN_WIDTH).coerceAtMost(MAX_WIDTH)
|
||||
val height = filterInput(mapHeight.text.toString(), MIN_HEIGHT).coerceAtMost(MAX_HEIGHT)
|
||||
val mines = filterInput(mapMines.text.toString(), MIN_MINES).coerceAtMost(width * height - 1)
|
||||
|
||||
return Minefield(width, height, mines)
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
private fun createView(): View {
|
||||
val view = LayoutInflater
|
||||
.from(context)
|
||||
.inflate(R.layout.dialog_custom_game, null, false)
|
||||
|
||||
mapWidth = view.findViewById(R.id.map_width)
|
||||
mapHeight = view.findViewById(R.id.map_height)
|
||||
mapMines = view.findViewById(R.id.map_mines)
|
||||
|
||||
createGameViewModel.singleState().let {
|
||||
mapWidth.text = it.width.toString()
|
||||
mapHeight.text = it.height.toString()
|
||||
mapMines.text = it.mines.toString()
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return AlertDialog.Builder(requireContext()).apply {
|
||||
setTitle(R.string.new_game)
|
||||
setView(R.layout.dialog_custom_game)
|
||||
setView(createView())
|
||||
setNegativeButton(R.string.cancel, null)
|
||||
setPositiveButton(R.string.start) { _, _ ->
|
||||
val minefield = getSelectedMinefield()
|
||||
createGameViewModel.sendEvent(CustomEvent.UpdateCustomGameEvent(minefield))
|
||||
viewModel.startNewGame(Difficulty.Custom)
|
||||
gameViewModel.startNewGame(Difficulty.Custom)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
package dev.lucasnlm.antimine.custom.viewmodel
|
||||
|
||||
import androidx.hilt.lifecycle.ViewModelInject
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import dev.lucasnlm.antimine.core.viewmodel.StatelessViewModel
|
||||
import dev.lucasnlm.antimine.core.viewmodel.IntentViewModel
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
class CreateGameViewModel @ViewModelInject constructor(
|
||||
class CreateGameViewModel(
|
||||
private val preferencesRepository: IPreferencesRepository
|
||||
) : StatelessViewModel<CustomEvent>() {
|
||||
override fun onEvent(event: CustomEvent) {
|
||||
when (event) {
|
||||
is CustomEvent.UpdateCustomGameEvent -> {
|
||||
preferencesRepository.updateCustomGameMode(event.minefield)
|
||||
}
|
||||
) : IntentViewModel<CustomEvent, CustomState>() {
|
||||
|
||||
override suspend fun mapEventToState(event: CustomEvent) = flow {
|
||||
if (event is CustomEvent.UpdateCustomGameEvent) {
|
||||
val minefield = event.minefield
|
||||
preferencesRepository.updateCustomGameMode(minefield)
|
||||
emit(CustomState(minefield.width, minefield.height, minefield.mines))
|
||||
}
|
||||
}
|
||||
|
||||
override fun initialState() = with(preferencesRepository.customGameMode()) {
|
||||
CustomState(width, height, mines)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package dev.lucasnlm.antimine.custom.viewmodel
|
||||
|
||||
data class CustomState(
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val mines: Int
|
||||
)
|
|
@ -1,18 +1,34 @@
|
|||
package dev.lucasnlm.antimine.di
|
||||
|
||||
import android.content.Context
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ApplicationComponent
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dev.lucasnlm.antimine.instant.InstantAppManager
|
||||
import dev.lucasnlm.antimine.common.BuildConfig
|
||||
import dev.lucasnlm.antimine.core.analytics.DebugAnalyticsManager
|
||||
import dev.lucasnlm.antimine.core.analytics.IAnalyticsManager
|
||||
import dev.lucasnlm.antimine.core.analytics.ProdAnalyticsManager
|
||||
import dev.lucasnlm.antimine.share.ShareManager
|
||||
import dev.lucasnlm.external.BillingManager
|
||||
import dev.lucasnlm.external.ExternalAnalyticsWrapper
|
||||
import dev.lucasnlm.external.IBillingManager
|
||||
import dev.lucasnlm.external.IInstantAppManager
|
||||
import dev.lucasnlm.external.IPlayGamesManager
|
||||
import dev.lucasnlm.external.InstantAppManager
|
||||
import dev.lucasnlm.external.PlayGamesManager
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
|
||||
@Module
|
||||
@InstallIn(ApplicationComponent::class)
|
||||
class AppModule {
|
||||
@Provides
|
||||
fun provideInstantAppManager(
|
||||
@ApplicationContext context: Context
|
||||
): InstantAppManager = InstantAppManager(context)
|
||||
val AppModule = module {
|
||||
single { InstantAppManager() } bind IInstantAppManager::class
|
||||
|
||||
single { BillingManager(get()) } bind IBillingManager::class
|
||||
|
||||
single { PlayGamesManager(get()) } bind IPlayGamesManager::class
|
||||
|
||||
single { ShareManager(get()) }
|
||||
|
||||
single {
|
||||
if (BuildConfig.DEBUG) {
|
||||
DebugAnalyticsManager()
|
||||
} else {
|
||||
ProdAnalyticsManager(ExternalAnalyticsWrapper(get()))
|
||||
}
|
||||
} bind IAnalyticsManager::class
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package dev.lucasnlm.antimine.di
|
||||
|
||||
import dev.lucasnlm.antimine.about.viewmodel.AboutViewModel
|
||||
import dev.lucasnlm.antimine.common.level.viewmodel.GameViewModel
|
||||
import dev.lucasnlm.antimine.control.viewmodel.ControlViewModel
|
||||
import dev.lucasnlm.antimine.custom.viewmodel.CreateGameViewModel
|
||||
import dev.lucasnlm.antimine.history.viewmodel.HistoryViewModel
|
||||
import dev.lucasnlm.antimine.level.viewmodel.EndGameDialogViewModel
|
||||
import dev.lucasnlm.antimine.playgames.viewmodel.PlayGamesViewModel
|
||||
import dev.lucasnlm.antimine.stats.viewmodel.StatsViewModel
|
||||
import dev.lucasnlm.antimine.text.viewmodel.TextViewModel
|
||||
import dev.lucasnlm.antimine.theme.viewmodel.ThemeViewModel
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
|
||||
val ViewModelModule = module {
|
||||
viewModel { AboutViewModel(get()) }
|
||||
viewModel { ControlViewModel(get()) }
|
||||
viewModel { CreateGameViewModel(get()) }
|
||||
viewModel { HistoryViewModel(get(), get()) }
|
||||
viewModel { EndGameDialogViewModel(get()) }
|
||||
viewModel { PlayGamesViewModel(get(), get()) }
|
||||
viewModel { StatsViewModel(get(), get()) }
|
||||
viewModel { TextViewModel(get()) }
|
||||
viewModel { ThemeViewModel(get()) }
|
||||
viewModel {
|
||||
GameViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
|
||||
}
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
package dev.lucasnlm.antimine.history
|
||||
|
||||
import android.os.Bundle
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.ThematicActivity
|
||||
import dev.lucasnlm.antimine.history.views.HistoryFragment
|
||||
|
||||
@AndroidEntryPoint
|
||||
class HistoryActivity : ThematicActivity(R.layout.activity_empty) {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
|
@ -3,15 +3,13 @@ package dev.lucasnlm.antimine.history.viewmodel
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.hilt.lifecycle.ViewModelInject
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dev.lucasnlm.antimine.DeepLink
|
||||
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||
import dev.lucasnlm.antimine.core.viewmodel.IntentViewModel
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
class HistoryViewModel @ViewModelInject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
class HistoryViewModel(
|
||||
private val context: Context,
|
||||
private val savesRepository: ISavesRepository
|
||||
) : IntentViewModel<HistoryEvent, HistoryState>() {
|
||||
|
||||
|
@ -31,7 +29,7 @@ class HistoryViewModel @ViewModelInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun mapEventToState(event: HistoryEvent) = flow<HistoryState> {
|
||||
override suspend fun mapEventToState(event: HistoryEvent) = flow {
|
||||
when (event) {
|
||||
is HistoryEvent.LoadAllSaves -> {
|
||||
val newSaveList = savesRepository.getAllSaves().sortedByDescending { it.uid }
|
||||
|
|
|
@ -3,20 +3,18 @@ package dev.lucasnlm.antimine.history.views
|
|||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.history.viewmodel.HistoryEvent
|
||||
import dev.lucasnlm.antimine.history.viewmodel.HistoryViewModel
|
||||
import kotlinx.android.synthetic.main.fragment_history.*
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
@AndroidEntryPoint
|
||||
class HistoryFragment : Fragment(R.layout.fragment_history) {
|
||||
private val historyViewModel: HistoryViewModel by activityViewModels()
|
||||
private val historyViewModel by viewModel<HistoryViewModel>()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
package dev.lucasnlm.antimine.instant
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import dev.lucasnlm.external.InstantAppWrapper
|
||||
|
||||
class InstantAppManager(
|
||||
private val context: Context
|
||||
) {
|
||||
fun isEnabled(): Boolean = InstantAppWrapper().isEnabled(context)
|
||||
|
||||
fun isNotEnabled(): Boolean = isEnabled().not()
|
||||
|
||||
fun showInstallPrompt(activity: Activity, intent: Intent?, requestCode: Int, referrer: String?) =
|
||||
InstantAppWrapper().showInstallPrompt(activity, intent, requestCode, referrer)
|
||||
}
|
|
@ -9,24 +9,22 @@ import android.widget.TextView
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatDialogFragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.common.level.viewmodel.GameViewModel
|
||||
import dev.lucasnlm.antimine.instant.InstantAppManager
|
||||
import dev.lucasnlm.antimine.level.viewmodel.EndGameDialogEvent
|
||||
import dev.lucasnlm.antimine.level.viewmodel.EndGameDialogViewModel
|
||||
import dev.lucasnlm.external.IInstantAppManager
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import javax.inject.Inject
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
@AndroidEntryPoint
|
||||
class EndGameDialogFragment : AppCompatDialogFragment() {
|
||||
@Inject
|
||||
lateinit var instantAppManager: InstantAppManager
|
||||
private val instantAppManager: IInstantAppManager by inject()
|
||||
|
||||
private val endGameViewModel by activityViewModels<EndGameDialogViewModel>()
|
||||
private val viewModel by activityViewModels<GameViewModel>()
|
||||
private val endGameViewModel by viewModel<EndGameDialogViewModel>()
|
||||
private val gameViewModel by sharedViewModel<GameViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -70,7 +68,7 @@ class EndGameDialogFragment : AppCompatDialogFragment() {
|
|||
}
|
||||
|
||||
when {
|
||||
instantAppManager.isEnabled() -> {
|
||||
instantAppManager.isEnabled(context) -> {
|
||||
setNeutralButton(R.string.install) { _, _ ->
|
||||
activity?.run {
|
||||
instantAppManager.showInstallPrompt(this, null, 0, null)
|
||||
|
@ -79,12 +77,12 @@ class EndGameDialogFragment : AppCompatDialogFragment() {
|
|||
}
|
||||
state.isVictory == true -> {
|
||||
setNeutralButton(R.string.share) { _, _ ->
|
||||
viewModel.shareObserver.postValue(Unit)
|
||||
gameViewModel.shareObserver.postValue(Unit)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
setNeutralButton(R.string.retry) { _, _ ->
|
||||
viewModel.retryObserver.postValue(Unit)
|
||||
gameViewModel.retryObserver.postValue(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +93,7 @@ class EndGameDialogFragment : AppCompatDialogFragment() {
|
|||
setView(view)
|
||||
|
||||
setPositiveButton(R.string.new_game) { _, _ ->
|
||||
viewModel.startNewGame()
|
||||
gameViewModel.startNewGame()
|
||||
}
|
||||
}.create()
|
||||
|
||||
|
|
|
@ -1,39 +1,57 @@
|
|||
package dev.lucasnlm.antimine.level.view
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import androidx.core.view.doOnLayout
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.lucasnlm.antimine.DeepLink
|
||||
import dev.lucasnlm.antimine.common.R
|
||||
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.view.CommonLevelFragment
|
||||
import dev.lucasnlm.antimine.common.level.view.SpaceItemDecoration
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@AndroidEntryPoint
|
||||
open class LevelFragment : CommonLevelFragment(R.layout.fragment_level) {
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
GlobalScope.launch {
|
||||
viewModel.saveGame()
|
||||
lifecycleScope.launch {
|
||||
gameViewModel.saveGame()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
recyclerGrid = view.findViewById(R.id.recyclerGrid)
|
||||
recyclerGrid.doOnLayout {
|
||||
lifecycleScope.launch {
|
||||
val loadGameUid = checkLoadGameDeepLink()
|
||||
val newGameDeepLink = checkNewGameDeepLink()
|
||||
val retryDeepLink = checkRetryGameDeepLink()
|
||||
|
||||
viewModel.run {
|
||||
val levelSetup = when {
|
||||
loadGameUid != null -> gameViewModel.loadGame(loadGameUid)
|
||||
newGameDeepLink != null -> gameViewModel.startNewGame(newGameDeepLink)
|
||||
retryDeepLink != null -> gameViewModel.retryGame(retryDeepLink)
|
||||
else -> gameViewModel.loadLastGame()
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
recyclerGrid.apply {
|
||||
addItemDecoration(SpaceItemDecoration(R.dimen.field_padding))
|
||||
setHasFixedSize(true)
|
||||
}
|
||||
setupRecyclerViewSize(levelSetup)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gameViewModel.run {
|
||||
field.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer {
|
||||
|
@ -44,20 +62,7 @@ open class LevelFragment : CommonLevelFragment(R.layout.fragment_level) {
|
|||
levelSetup.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer {
|
||||
recyclerGrid.apply {
|
||||
val horizontalPadding = calcHorizontalPadding(it.width)
|
||||
val verticalPadding = calcVerticalPadding(it.height)
|
||||
layoutManager = makeNewLayoutManager(it.width)
|
||||
setHasFixedSize(true)
|
||||
setPadding(horizontalPadding, verticalPadding, 0, 0)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
fieldRefresh.observe(
|
||||
viewLifecycleOwner,
|
||||
Observer {
|
||||
areaAdapter.notifyItemChanged(it)
|
||||
setupRecyclerViewSize(it)
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -81,38 +86,19 @@ open class LevelFragment : CommonLevelFragment(R.layout.fragment_level) {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
private fun setupRecyclerViewSize(levelSetup: Minefield) {
|
||||
recyclerGrid.apply {
|
||||
val horizontalPadding = calcHorizontalPadding(levelSetup.width)
|
||||
val verticalPadding = calcVerticalPadding(levelSetup.height)
|
||||
setPadding(horizontalPadding, verticalPadding, 0, 0)
|
||||
layoutManager = makeNewLayoutManager(levelSetup.width)
|
||||
adapter = areaAdapter
|
||||
alpha = 0.0f
|
||||
|
||||
lifecycleScope.launchWhenCreated {
|
||||
val loadGameUid = checkLoadGameDeepLink()
|
||||
val newGameDeepLink = checkNewGameDeepLink()
|
||||
val retryDeepLink = checkRetryGameDeepLink()
|
||||
|
||||
val levelSetup = when {
|
||||
loadGameUid != null -> viewModel.loadGame(loadGameUid)
|
||||
newGameDeepLink != null -> viewModel.startNewGame(newGameDeepLink)
|
||||
retryDeepLink != null -> viewModel.retryGame(retryDeepLink)
|
||||
else -> viewModel.loadLastGame()
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
recyclerGrid.apply {
|
||||
val horizontalPadding = calcHorizontalPadding(levelSetup.width)
|
||||
val verticalPadding = calcVerticalPadding(levelSetup.height)
|
||||
addItemDecoration(SpaceItemDecoration(R.dimen.field_padding))
|
||||
setPadding(horizontalPadding, verticalPadding, 0, 0)
|
||||
layoutManager = makeNewLayoutManager(levelSetup.width)
|
||||
setHasFixedSize(true)
|
||||
adapter = areaAdapter
|
||||
alpha = 0.0f
|
||||
|
||||
animate().apply {
|
||||
alpha(1.0f)
|
||||
duration = DateUtils.SECOND_IN_MILLIS
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
animate().apply {
|
||||
alpha(1.0f)
|
||||
duration = DateUtils.SECOND_IN_MILLIS
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package dev.lucasnlm.antimine.level.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import androidx.hilt.lifecycle.ViewModelInject
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.core.viewmodel.IntentViewModel
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
class EndGameDialogViewModel @ViewModelInject constructor(
|
||||
@ApplicationContext private val context: Context
|
||||
class EndGameDialogViewModel(
|
||||
private val context: Context
|
||||
) : IntentViewModel<EndGameDialogEvent, EndGameDialogState>() {
|
||||
|
||||
private fun List<Int>.safeRandomEmoji(
|
||||
|
@ -28,6 +26,7 @@ class EndGameDialogViewModel @ViewModelInject constructor(
|
|||
R.drawable.emoji_grinning_squinting_face,
|
||||
R.drawable.emoji_smiling_face_with_sunglasses,
|
||||
R.drawable.emoji_squinting_face_with_tongue,
|
||||
R.drawable.emoji_hugging_face,
|
||||
R.drawable.emoji_partying_face,
|
||||
R.drawable.emoji_clapping_hands,
|
||||
R.drawable.emoji_triangular_flag
|
||||
|
@ -74,7 +73,7 @@ class EndGameDialogViewModel @ViewModelInject constructor(
|
|||
false
|
||||
)
|
||||
|
||||
override suspend fun mapEventToState(event: EndGameDialogEvent) = flow<EndGameDialogState> {
|
||||
override suspend fun mapEventToState(event: EndGameDialogEvent) = flow {
|
||||
if (event is EndGameDialogEvent.BuildCustomEndGame) {
|
||||
val state = when (event.isVictory) {
|
||||
true -> {
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
package dev.lucasnlm.antimine.playgames
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.playgames.viewmodel.PlayGamesEvent
|
||||
import dev.lucasnlm.antimine.playgames.viewmodel.PlayGamesViewModel
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class PlayGamesDialogFragment : DialogFragment() {
|
||||
private val playGamesViewModel by viewModel<PlayGamesViewModel>()
|
||||
private val adapter by lazy { PlayGamesAdapter(playGamesViewModel) }
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return AlertDialog.Builder(requireContext()).apply {
|
||||
setTitle(R.string.google_play_games)
|
||||
setAdapter(adapter, null)
|
||||
setPositiveButton(R.string.ok, null)
|
||||
}.create()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
lifecycleScope.launchWhenCreated {
|
||||
playGamesViewModel.observeEvent().collect {
|
||||
when (it) {
|
||||
is PlayGamesEvent.OpenAchievements -> {
|
||||
activity?.let { activity ->
|
||||
playGamesViewModel.openAchievements(activity)
|
||||
}
|
||||
}
|
||||
is PlayGamesEvent.OpenLeaderboards -> {
|
||||
activity?.let { activity ->
|
||||
playGamesViewModel.openLeaderboards(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PlayGamesAdapter(
|
||||
private val playGamesViewModel: PlayGamesViewModel
|
||||
) : BaseAdapter() {
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
val view = if (convertView == null) {
|
||||
PlayGamesButton(parent!!.context)
|
||||
} else {
|
||||
(convertView as PlayGamesButton)
|
||||
}
|
||||
|
||||
val item = playGamesViewModel.playGamesItems[position]
|
||||
|
||||
return view.apply {
|
||||
text.text = view.context.getString(item.stringRes)
|
||||
icon.setImageResource(item.iconRes)
|
||||
|
||||
setOnClickListener {
|
||||
playGamesViewModel.sendEvent(item.triggerEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasStableIds(): Boolean = true
|
||||
|
||||
override fun getItem(position: Int): Any = playGamesViewModel.playGamesItems[position]
|
||||
|
||||
override fun getItemId(position: Int): Long = playGamesViewModel.playGamesItems[position].id.toLong()
|
||||
|
||||
override fun getCount(): Int = playGamesViewModel.playGamesItems.count()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = PlayGamesDialogFragment::class.simpleName
|
||||
}
|
||||
}
|
||||
|
||||
class PlayGamesButton : FrameLayout {
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
init {
|
||||
inflate(context, R.layout.view_play_games_button, this)
|
||||
|
||||
icon = findViewById(R.id.icon)
|
||||
text = findViewById(R.id.text)
|
||||
}
|
||||
|
||||
val icon: AppCompatImageView
|
||||
val text: TextView
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package dev.lucasnlm.antimine.playgames.model
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import dev.lucasnlm.antimine.playgames.viewmodel.PlayGamesEvent
|
||||
|
||||
data class PlayGamesItem(
|
||||
val id: Int,
|
||||
@DrawableRes val iconRes: Int,
|
||||
@StringRes val stringRes: Int,
|
||||
val triggerEvent: PlayGamesEvent
|
||||
)
|
|
@ -0,0 +1,6 @@
|
|||
package dev.lucasnlm.antimine.playgames.viewmodel
|
||||
|
||||
sealed class PlayGamesEvent {
|
||||
object OpenAchievements : PlayGamesEvent()
|
||||
object OpenLeaderboards : PlayGamesEvent()
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package dev.lucasnlm.antimine.playgames.viewmodel
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.core.viewmodel.StatelessViewModel
|
||||
import dev.lucasnlm.antimine.playgames.model.PlayGamesItem
|
||||
import dev.lucasnlm.external.IPlayGamesManager
|
||||
|
||||
class PlayGamesViewModel(
|
||||
private val context: Context,
|
||||
private val playGamesManager: IPlayGamesManager
|
||||
) : StatelessViewModel<PlayGamesEvent>() {
|
||||
|
||||
val playGamesItems = listOf(
|
||||
PlayGamesItem(0, R.drawable.games_achievements, R.string.achievements, PlayGamesEvent.OpenAchievements),
|
||||
PlayGamesItem(1, R.drawable.games_leaderboards, R.string.leaderboards, PlayGamesEvent.OpenLeaderboards)
|
||||
)
|
||||
|
||||
fun openAchievements(activity: Activity) {
|
||||
playGamesManager.openAchievements(activity)
|
||||
}
|
||||
|
||||
fun openLeaderboards(activity: Activity) {
|
||||
playGamesManager.openLeaderboards(activity)
|
||||
}
|
||||
}
|
|
@ -3,11 +3,9 @@ package dev.lucasnlm.antimine.preferences
|
|||
import android.os.Bundle
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.ThematicActivity
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PreferencesActivity : ThematicActivity(R.layout.activity_empty) {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.graphics.Color
|
|||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
import android.graphics.Typeface
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.FileProvider
|
||||
import dev.lucasnlm.antimine.BuildConfig
|
||||
import dev.lucasnlm.antimine.R
|
||||
|
@ -22,12 +23,10 @@ import kotlinx.coroutines.withContext
|
|||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
class ShareBuilder(
|
||||
context: Context
|
||||
class ShareManager(
|
||||
private val context: Context
|
||||
) {
|
||||
private val context: Context = context.applicationContext
|
||||
|
||||
suspend fun share(minefield: Minefield, field: List<Area>): Boolean {
|
||||
private suspend fun share(minefield: Minefield, field: List<Area>): Boolean {
|
||||
val file = createImage(minefield, field)
|
||||
|
||||
return if (file != null) {
|
||||
|
@ -129,4 +128,16 @@ class ShareBuilder(
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun shareField(minefield: Minefield?, field: List<Area>?) {
|
||||
val result = if (minefield != null && field != null && field.count() != 0) {
|
||||
share(minefield, field)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
Toast.makeText(context, context.getString(R.string.fail_to_share), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package dev.lucasnlm.antimine.share.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.share.ShareBuilder
|
||||
import dev.lucasnlm.antimine.common.level.models.Area
|
||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
|
||||
class ShareViewModel(
|
||||
application: Application
|
||||
) : AndroidViewModel(application) {
|
||||
private val context = getApplication<Application>().applicationContext
|
||||
|
||||
suspend fun share(minefield: Minefield?, field: List<Area>?) {
|
||||
val result = if (minefield != null && field != null && field.count() != 0) {
|
||||
ShareBuilder(context).share(minefield, field)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
Toast.makeText(context, context.getString(R.string.fail_to_share), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,10 +3,8 @@ 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.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.ThematicActivity
|
||||
import dev.lucasnlm.antimine.stats.model.StatsModel
|
||||
|
@ -16,19 +14,19 @@ import kotlinx.android.synthetic.main.activity_stats.*
|
|||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
@AndroidEntryPoint
|
||||
class StatsActivity : ThematicActivity(R.layout.activity_stats) {
|
||||
private val viewModel: StatsViewModel by viewModels()
|
||||
private val statsViewModel by viewModel<StatsViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
refreshStats(StatsViewModel.emptyStats)
|
||||
|
||||
lifecycleScope.launchWhenResumed {
|
||||
viewModel.sendEvent(StatsEvent.LoadStats)
|
||||
statsViewModel.sendEvent(StatsEvent.LoadStats)
|
||||
|
||||
viewModel.observeState().collect {
|
||||
statsViewModel.observeState().collect {
|
||||
refreshStats(it)
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +59,7 @@ class StatsActivity : ThematicActivity(R.layout.activity_stats) {
|
|||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
viewModel.singleState().let {
|
||||
statsViewModel.singleState().let {
|
||||
if (it.totalGames > 0) {
|
||||
menuInflater.inflate(R.menu.stats_menu, menu)
|
||||
}
|
||||
|
@ -85,7 +83,7 @@ class StatsActivity : ThematicActivity(R.layout.activity_stats) {
|
|||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete_all) { _, _ ->
|
||||
GlobalScope.launch {
|
||||
viewModel.sendEvent(StatsEvent.DeleteStats)
|
||||
statsViewModel.sendEvent(StatsEvent.DeleteStats)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
package dev.lucasnlm.antimine.stats.viewmodel
|
||||
|
||||
import androidx.hilt.lifecycle.ViewModelInject
|
||||
import dev.lucasnlm.antimine.common.level.repository.IStatsRepository
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import dev.lucasnlm.antimine.core.viewmodel.IntentViewModel
|
||||
import dev.lucasnlm.antimine.stats.model.StatsModel
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
class StatsViewModel @ViewModelInject constructor(
|
||||
class StatsViewModel(
|
||||
private val statsRepository: IStatsRepository,
|
||||
private val preferenceRepository: IPreferencesRepository
|
||||
) : IntentViewModel<StatsEvent, StatsModel>() {
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package dev.lucasnlm.antimine.support
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatDialogFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.external.IBillingManager
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
class SupportAppDialogFragment : AppCompatDialogFragment() {
|
||||
private val billingManager: IBillingManager by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
billingManager.start()
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return AlertDialog.Builder(requireContext()).apply {
|
||||
setView(R.layout.dialog_payments)
|
||||
setNeutralButton(R.string.rating_button_no, null)
|
||||
setPositiveButton(R.string.unlock) { _, _ ->
|
||||
lifecycleScope.launch {
|
||||
billingManager.charge(requireActivity())
|
||||
}
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = SupportAppDialogFragment::class.simpleName
|
||||
}
|
||||
}
|
|
@ -4,10 +4,8 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.RawRes
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.ThematicActivity
|
||||
import dev.lucasnlm.antimine.text.viewmodel.TextEvent
|
||||
|
@ -16,10 +14,10 @@ import kotlinx.android.synthetic.main.activity_text.*
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
@AndroidEntryPoint
|
||||
class TextActivity : ThematicActivity(R.layout.activity_text) {
|
||||
private val viewModel: TextViewModel by viewModels()
|
||||
private val textViewModel by viewModel<TextViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -28,7 +26,7 @@ class TextActivity : ThematicActivity(R.layout.activity_text) {
|
|||
title = bundle.getString(TEXT_TITLE)
|
||||
|
||||
lifecycleScope.launchWhenCreated {
|
||||
viewModel.sendEvent(
|
||||
textViewModel.sendEvent(
|
||||
TextEvent.LoadText(
|
||||
title = bundle.getString(TEXT_TITLE, ""),
|
||||
rawFileRes = bundle.getInt(TEXT_PATH, -1)
|
||||
|
@ -39,7 +37,7 @@ class TextActivity : ThematicActivity(R.layout.activity_text) {
|
|||
progressBar.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
viewModel.observeState().collect {
|
||||
textViewModel.observeState().collect {
|
||||
textView.text = it.body
|
||||
progressBar.visibility = View.GONE
|
||||
}
|
||||
|
|
|
@ -2,16 +2,14 @@ package dev.lucasnlm.antimine.text.viewmodel
|
|||
|
||||
import android.content.Context
|
||||
import androidx.annotation.RawRes
|
||||
import androidx.hilt.lifecycle.ViewModelInject
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dev.lucasnlm.antimine.core.viewmodel.IntentViewModel
|
||||
import dev.lucasnlm.antimine.text.models.TextState
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class TextViewModel @ViewModelInject constructor(
|
||||
@ApplicationContext private val context: Context
|
||||
class TextViewModel(
|
||||
private val context: Context
|
||||
) : IntentViewModel<TextEvent, TextState>() {
|
||||
|
||||
private suspend fun loadText(@RawRes rawFile: Int): String {
|
||||
|
|
|
@ -1,26 +1,24 @@
|
|||
package dev.lucasnlm.antimine.theme
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.lucasnlm.antimine.R
|
||||
import dev.lucasnlm.antimine.ThematicActivity
|
||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||
import dev.lucasnlm.antimine.common.level.view.SpaceItemDecoration
|
||||
import dev.lucasnlm.antimine.support.SupportAppDialogFragment
|
||||
import dev.lucasnlm.antimine.theme.view.ThemeAdapter
|
||||
import dev.lucasnlm.antimine.theme.viewmodel.ThemeViewModel
|
||||
import kotlinx.android.synthetic.main.activity_theme.*
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import javax.inject.Inject
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ThemeActivity : ThematicActivity(R.layout.activity_theme) {
|
||||
@Inject
|
||||
lateinit var dimensionRepository: IDimensionRepository
|
||||
private val dimensionRepository: IDimensionRepository by inject()
|
||||
|
||||
private val viewModel by viewModels<ThemeViewModel>()
|
||||
private val themeViewModel by viewModel<ThemeViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -32,14 +30,22 @@ class ThemeActivity : ThematicActivity(R.layout.activity_theme) {
|
|||
addItemDecoration(SpaceItemDecoration(R.dimen.theme_divider))
|
||||
setHasFixedSize(true)
|
||||
layoutManager = GridLayoutManager(context, 3)
|
||||
adapter = ThemeAdapter(viewModel, areaSize)
|
||||
adapter = ThemeAdapter(themeViewModel, areaSize)
|
||||
}
|
||||
|
||||
viewModel.observeState().collect {
|
||||
themeViewModel.observeState().collect {
|
||||
if (usingTheme.id != it.current.id) {
|
||||
recreate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showUnlockDialog() {
|
||||
if (supportFragmentManager.findFragmentByTag(SupportAppDialogFragment.TAG) == null) {
|
||||
SupportAppDialogFragment().apply {
|
||||
show(supportFragmentManager, SupportAppDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,14 +58,27 @@ class ThemeAdapter(
|
|||
val theme = themes[position]
|
||||
val paintSettings = createAreaPaintSettings(holder.itemView.context, areaSize)
|
||||
holder.itemView.run {
|
||||
val selected = (theme.id == themeViewModel.singleState().current.id)
|
||||
val areas = listOf(area0, area1, area2, area3, area4, area5, area6, area7, area8)
|
||||
|
||||
if (selected) {
|
||||
areas.forEach { it.alpha = 0.25f }
|
||||
} else {
|
||||
areas.forEach { it.alpha = 1.0f }
|
||||
}
|
||||
|
||||
areas.forEachIndexed { index, areaView -> areaView.bindTheme(minefield[index], theme, paintSettings) }
|
||||
|
||||
if (position == 0) {
|
||||
if (position < 2 && !selected) {
|
||||
areas.forEach { it.alpha = 0.35f }
|
||||
|
||||
label.apply {
|
||||
text = if (position == 0) {
|
||||
label.context.getString(R.string.system)
|
||||
} else {
|
||||
label.context.getString(R.string.amoled)
|
||||
}
|
||||
|
||||
setTextColor(
|
||||
with(theme.palette.background) {
|
||||
Color.rgb(255 - Color.red(this), 255 - Color.green(this), 255 - Color.blue(this))
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
package dev.lucasnlm.antimine.theme.viewmodel
|
||||
|
||||
import androidx.hilt.lifecycle.ViewModelInject
|
||||
import dev.lucasnlm.antimine.core.themes.model.AppTheme
|
||||
import dev.lucasnlm.antimine.core.themes.repository.IThemeRepository
|
||||
import dev.lucasnlm.antimine.core.viewmodel.IntentViewModel
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
class ThemeViewModel @ViewModelInject constructor(
|
||||
class ThemeViewModel(
|
||||
private val themeRepository: IThemeRepository
|
||||
) : IntentViewModel<ThemeEvent, ThemeState>() {
|
||||
private fun setTheme(theme: AppTheme) {
|
||||
themeRepository.setTheme(theme)
|
||||
}
|
||||
|
||||
override suspend fun mapEventToState(event: ThemeEvent) = flow<ThemeState> {
|
||||
override suspend fun mapEventToState(event: ThemeEvent) = flow {
|
||||
if (event is ThemeEvent.ChangeTheme) {
|
||||
setTheme(event.newTheme)
|
||||
emit(state.copy(current = event.newTheme))
|
||||
|
|
359
app/src/main/res/drawable/emoji_hugging_face.xml
Normal file
|
@ -0,0 +1,359 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="128dp"
|
||||
android:height="128dp"
|
||||
android:viewportWidth="128"
|
||||
android:viewportHeight="128">
|
||||
<path
|
||||
android:pathData="M63.6,118.8c-27.9,0 -58,-17.5 -58,-55.9S35.7,7 63.6,7c15.5,0 29.8,5.1 40.4,14.4c11.5,10.2 17.6,24.6 17.6,41.5s-6.1,31.2 -17.6,41.4c-10.6,9.3 -25,14.5 -40.4,14.5z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:gradientRadius="56.96"
|
||||
android:centerX="63.6"
|
||||
android:centerY="62.9"
|
||||
android:type="radial">
|
||||
<item android:offset="0.5" android:color="#FFFDE030"/>
|
||||
<item android:offset="0.919" android:color="#FFF7C02B"/>
|
||||
<item android:offset="1" android:color="#FFF4A223"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M63.6,118.8c-27.9,0 -58,-17.5 -58,-55.9S35.7,7 63.6,7c15.5,0 29.8,5.1 40.4,14.4c11.5,10.2 17.6,24.6 17.6,41.5s-6.1,31.2 -17.6,41.4c-10.6,9.3 -25,14.5 -40.4,14.5z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="118.8"
|
||||
android:startX="63.6"
|
||||
android:endY="7"
|
||||
android:endX="63.6"
|
||||
android:type="linear">
|
||||
<item android:offset="0.158" android:color="#FFF4A223"/>
|
||||
<item android:offset="0.333" android:color="#FFF7C02B"/>
|
||||
<item android:offset="0.807" android:color="#00FDE030"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M111.49,29.67c5.33,8.6 8.11,18.84 8.11,30.23c0,16.9 -6.1,31.2 -17.6,41.4c-10.6,9.3 -25,14.5 -40.4,14.5c-18.06,0 -37.04,-7.35 -48.18,-22.94c10.76,17.66 30.99,25.94 50.18,25.94c15.4,0 29.8,-5.2 40.4,-14.5c11.5,-10.2 17.6,-24.5 17.6,-41.4c0,-12.74 -3.47,-24.06 -10.11,-33.23z"
|
||||
android:fillColor="#eb8f00"/>
|
||||
<path
|
||||
android:pathData="M29.7,64.1m-16.3,0a16.3,16.3 0,1 1,32.6 0a16.3,16.3 0,1 1,-32.6 0"
|
||||
android:strokeAlpha="0.8"
|
||||
android:fillAlpha="0.8">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:gradientRadius="17.73736"
|
||||
android:centerX="29.6654"
|
||||
android:centerY="64.10675"
|
||||
android:type="radial">
|
||||
<item android:offset="0" android:color="#FFED7770"/>
|
||||
<item android:offset="0.9" android:color="#00ED7770"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M96.9,51.5m-16.3,0a16.3,16.3 0,1 1,32.6 0a16.3,16.3 0,1 1,-32.6 0"
|
||||
android:strokeAlpha="0.8"
|
||||
android:fillAlpha="0.8">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:gradientRadius="17.735403"
|
||||
android:centerX="96.90706"
|
||||
android:centerY="51.534607"
|
||||
android:type="radial">
|
||||
<item android:offset="0" android:color="#FFED7770"/>
|
||||
<item android:offset="0.9" android:color="#00ED7770"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M96.9,49.4C89.2,56.5 79,62.1 67.2,64.8c-11.8,2.7 -23.4,2.2 -33.4,-0.9c-1.8,-0.6 -3.1,1.8 -1.5,3c10.1,7.5 23.6,10.7 37.1,7.5c13.5,-3.1 24.3,-11.8 30.1,-23c1,-1.7 -1.2,-3.3 -2.6,-2z"
|
||||
android:fillColor="#422b0d"/>
|
||||
<path
|
||||
android:pathData="M48.5,47.2l-0.2,-0.2c-0.1,-0.1 -0.3,-0.2 -0.5,-0.4c-0.2,-0.1 -0.4,-0.3 -0.6,-0.4c-0.2,-0.2 -0.5,-0.4 -0.8,-0.6c-0.3,-0.2 -0.6,-0.4 -0.9,-0.5c-0.3,-0.2 -0.6,-0.3 -0.9,-0.3c-0.3,-0.1 -0.5,-0.1 -0.6,-0.1h-0.2h-0.1h0.2l-0.5,0.1c-0.1,0 0,0 0,0h0.1c0.1,0 0,0 0,0h-0.1c-0.1,0.1 -0.3,0.2 -0.5,0.3c-0.2,0.2 -0.5,0.4 -0.7,0.6c-0.2,0.2 -0.4,0.5 -0.6,0.8c-0.4,0.6 -0.7,1.1 -0.9,1.5s-0.4,0.6 -0.4,0.6l-0.2,0.4c-1,1.7 -3.2,2.4 -5,1.5c-1.2,-0.6 -1.9,-1.7 -2,-2.9v-1.1c0.1,-0.7 0.2,-1.7 0.6,-2.9s1.1,-2.7 2.4,-4.1c0.7,-0.7 1.5,-1.5 2.5,-2c0.2,-0.2 0.5,-0.3 0.8,-0.4c0.3,-0.1 0.5,-0.3 0.9,-0.4l0.5,-0.2c0.2,-0.1 0.4,-0.1 0.5,-0.1l0.5,-0.1l0.3,-0.1h0.4l0.5,-0.1h0.9c0.6,0 1.2,0 1.8,0.1c1.2,0.2 2.2,0.6 3.1,1c1.8,0.9 2.9,2 3.8,2.9c0.4,0.5 0.8,0.9 1,1.3c0.3,0.4 0.5,0.8 0.7,1.1s0.2,0.5 0.3,0.6c0,0.1 0.1,0.2 0.1,0.2c0.7,1.8 -0.3,3.8 -2.3,4.5c-1.4,0.5 -2.9,0.2 -3.9,-0.6z"
|
||||
android:fillColor="#422b0d"/>
|
||||
<path
|
||||
android:pathData="M82.8,40.5l-0.2,-0.2c-0.1,-0.1 -0.3,-0.2 -0.5,-0.4c-0.2,-0.1 -0.4,-0.3 -0.6,-0.4c-0.2,-0.2 -0.5,-0.4 -0.8,-0.6c-0.3,-0.2 -0.6,-0.4 -0.9,-0.5c-0.3,-0.2 -0.6,-0.3 -0.9,-0.3c-0.2,-0.1 -0.4,-0.1 -0.5,-0.1H78.1h0.2l-0.5,0.1c-0.1,0 0,0 0,0h0.1c0.1,0 0,0 0,0h-0.1c-0.1,0.1 -0.3,0.2 -0.6,0.3c-0.2,0.2 -0.4,0.4 -0.7,0.6c-0.2,0.2 -0.4,0.5 -0.6,0.8c-0.4,0.6 -0.7,1.1 -0.9,1.5c-0.2,0.4 -0.4,0.6 -0.4,0.6l-0.2,0.3c-1,1.7 -3.2,2.4 -5,1.5c-1.2,-0.6 -1.9,-1.7 -2,-2.9c0,0 0,-0.4 0.1,-1.1c0.1,-0.7 0.2,-1.7 0.6,-2.9s1.1,-2.7 2.4,-4.1c0.7,-0.7 1.5,-1.5 2.5,-2c0.2,-0.1 0.5,-0.3 0.8,-0.4s0.5,-0.3 0.9,-0.4l0.6,-0.2c0.2,-0.1 0.4,-0.1 0.5,-0.1l0.5,-0.1l0.3,-0.1h0.4l0.5,-0.1h0.9c0.6,0 1.2,0 1.8,0.1c1.2,0.2 2.2,0.6 3.1,1c1.8,0.9 2.9,2 3.8,2.9c0.4,0.5 0.8,0.9 1,1.3c0.3,0.4 0.5,0.8 0.7,1.1s0.2,0.5 0.3,0.6c0.1,0.1 0.1,0.2 0.1,0.2c0.7,1.8 -0.3,3.8 -2.3,4.5c-1.6,0.7 -3.1,0.4 -4.1,-0.5z"
|
||||
android:fillColor="#422b0d"/>
|
||||
<path
|
||||
android:pathData="M38.87,84.46C37.78,81.38 37,113.28 37,113.28c2.92,1.23 5.87,2.31 8.93,3.13c1.21,-2.73 2.79,-7.76 1.96,-13.46c-1.34,-9.11 -6.78,-12.14 -9.02,-18.49z"
|
||||
android:strokeAlpha="0.66"
|
||||
android:fillAlpha="0.66">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="128.189"
|
||||
android:startX="42.556"
|
||||
android:endY="97.296"
|
||||
android:endX="42.556"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FFBF360C"/>
|
||||
<item android:offset="1" android:color="#33BF360C"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M39.5,81.23c-0.49,-1.33 -1.11,-2.32 -2.68,-2.32c-0.72,0 -1.94,0.18 -2.59,1.17c-1.28,1.92 -0.78,5.52 0.2,8.65c0.55,1.77 0.4,2.42 0.4,2.42c-2.72,-0.17 -5.88,-3.88 -9.03,-7.58c-1.93,-2.27 -3.05,-4.36 -4.93,-6.68c-1.14,-1.41 -2.81,-2.83 -4.77,-2.11c-5.37,1.99 1.27,9.72 2.93,11.84c1.89,2.41 3.76,4.72 3.76,4.72c-1.76,-0.41 -2.87,-0.88 -5.97,-4.06c-2.65,-2.72 -4.62,-6.23 -6.06,-7.54c-3.27,-2.97 -5.27,-2 -6.14,-0.02c-0.81,1.84 0.53,5.09 1.44,6.61c4.51,7.48 10.27,10.41 10.27,10.41c-4.74,-0.21 -8.48,-5.29 -9.99,-6.83c-4.38,-4.45 -6.38,1.06 -3.99,5.05c1.1,1.83 2.2,3.12 4.21,5.19c3.25,3.33 6.31,4.84 6.31,4.84s-1.31,0.2 -2.63,-0.49c-1.78,-0.93 -3.77,-2.33 -4.8,-3.13c-2.95,-2.27 -5.76,-0.32 -4.63,2.7c1.31,3.49 6.09,6.45 8.98,8.6c5.62,4.16 12.63,7.49 19.73,7.26c7.9,-0.25 13.34,-5.68 15.48,-13.31c1.24,-4.44 -1.72,-11.5 -2.47,-13.28c-2.47,-5.76 -2.54,-10.78 -3.03,-12.11z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:gradientRadius="38.904"
|
||||
android:centerX="29.186"
|
||||
android:centerY="93.866"
|
||||
android:type="radial">
|
||||
<item android:offset="0.33" android:color="#FFFFF176"/>
|
||||
<item android:offset="1" android:color="#FFFFC400"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M23.62,99.08c0.14,8.38 5.4,11.88 9.48,12.65a13.771,13.771 0,0 1,-2.39 -8.2c0.1,-2.9 1.22,-6.51 2.88,-8.47c1.66,-1.96 1.24,-3.89 1.24,-3.89s-1.12,-0.09 -1.54,-0.34c-1.34,-0.82 -2.88,-1.48 -4.04,-2.55c-0.85,-0.79 -5.77,2.42 -5.63,10.8z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:gradientRadius="11.727577"
|
||||
android:centerX="36.794704"
|
||||
android:centerY="102.449486"
|
||||
android:type="radial">
|
||||
<item android:offset="0" android:color="#FFFFC709"/>
|
||||
<item android:offset="1" android:color="#00FFC709"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M6.4,78.54a2.84,2.84 0,0 0,-1.28 2.35c-0.02,0.91 0.25,1.82 0.58,2.68c0.7,1.73 1.8,3.28 2.93,4.77c1.16,1.48 2.41,2.91 3.71,4.29c1.3,1.39 2.63,2.76 3.98,4.13c-1.67,-0.98 -3.22,-2.16 -4.65,-3.46c-1.42,-1.31 -2.77,-2.73 -3.91,-4.31c-1.17,-1.57 -2.1,-3.32 -2.71,-5.2c-0.28,-0.94 -0.48,-1.95 -0.31,-2.95c0.12,-0.99 0.76,-1.94 1.66,-2.3z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="95.255"
|
||||
android:startX="18.517"
|
||||
android:endY="82.775"
|
||||
android:endX="5.827"
|
||||
android:type="linear">
|
||||
<item android:offset="0.127" android:color="#FFB7537A"/>
|
||||
<item android:offset="1" android:color="#FFFFC400"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M0.55,102.67c0.03,-0.28 0.07,-0.52 0.12,-0.73c-0.07,0.23 -0.11,0.47 -0.12,0.73z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:gradientRadius="38.904"
|
||||
android:centerX="29.186"
|
||||
android:centerY="93.866"
|
||||
android:type="radial">
|
||||
<item android:offset="0.33" android:color="#FFFFF176"/>
|
||||
<item android:offset="1" android:color="#FFFFC400"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M4.31,106.78c-0.72,-0.69 -1.21,-1.27 -1.58,-1.88c-0.42,-0.7 -0.66,-1.44 -0.68,-2.13c-0.02,-0.77 0.05,-1.34 0.78,-1.9c0.36,-0.28 1.37,-0.18 1.35,-0.19c-0.01,-0.01 -0.03,-0.01 -0.04,-0.02c-0.61,-0.24 -1.18,-0.3 -1.69,-0.22l0.01,-0.01l-0.01,0.01c-0.86,0.14 -1.52,0.69 -1.78,1.5c-0.05,0.21 -0.09,0.45 -0.12,0.73c-0.01,0.43 0.06,0.91 0.25,1.42c0.29,0.77 0.76,1.52 1.33,2.24c0.34,0.52 0.74,1.01 1.11,1.48c0.59,0.7 1.19,1.39 1.86,2.01a26.45,26.45 0,0 0,4.3 3.38c0.75,0.46 8.17,5.01 14.67,6.26c0.02,-0.02 -12.39,-5.14 -19.76,-12.68z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="109.935"
|
||||
android:startX="0.549"
|
||||
android:endY="109.935"
|
||||
android:endX="24.074"
|
||||
android:type="linear">
|
||||
<item android:offset="0.127" android:color="#FFDA6727"/>
|
||||
<item android:offset="1" android:color="#FFFFC400"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M13.25,105.53c-0.07,0 -0.15,-0.01 -0.21,-0.04c-1.58,-0.66 -3.15,-1.6 -4.81,-2.88a27.08,27.08 0,0 1,-4.01 -3.91c-0.64,-0.74 -1.21,-1.55 -1.67,-2.24l-0.14,-0.22c-0.47,-0.73 -0.95,-1.48 -1.25,-2.36c-0.39,-1.04 -0.49,-2.11 -0.27,-3.1c0.25,-1.19 0.93,-2 1.88,-2.41c1.36,-0.59 2.88,0.91 2.79,0.87c-1.05,-0.47 -1.97,-0.21 -2.21,-0.05c-0.55,0.38 -0.9,1.04 -0.97,1.81c-0.05,0.69 0.11,1.45 0.46,2.19c0.3,0.65 0.72,1.28 1.37,2.04c0.56,0.72 1.14,1.38 1.71,2.03a57.36,57.36 0,0 0,3.68 3.77c1.07,0.99 2.49,2.28 4.02,3.5c0.22,0.17 0.28,0.48 0.13,0.72c-0.12,0.18 -0.3,0.28 -0.5,0.28z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="96.876"
|
||||
android:startX="0.779"
|
||||
android:endY="96.876"
|
||||
android:endX="13.816"
|
||||
android:type="linear">
|
||||
<item android:offset="0.127" android:color="#FFDA6727"/>
|
||||
<item android:offset="1" android:color="#FFFFC400"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M16.78,96.91c0.19,-0.21 0.15,-0.34 -0.05,-0.54a218.71,218.71 0,0 1,-3.97 -4.12a61.13,61.13 0,0 1,-3.67 -4.24C8,86.58 6.9,85.04 6.22,83.37c-0.26,-0.68 -0.56,-1.58 -0.54,-2.47c0,-0.4 0.11,-0.77 0.29,-1.1c0.17,-0.31 0.42,-0.58 0.73,-0.78c0.01,0 0.01,-0.01 0.01,-0.01c0.51,-0.31 1.34,-0.54 2.67,-0.31c-2.33,-1.57 -4.08,-0.68 -4.82,1.01c-0.81,1.84 0.57,5.13 1.49,6.65c4.51,7.48 10.02,10.47 10.02,10.47c0.27,0.17 0.57,0.22 0.71,0.08z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="87.433"
|
||||
android:startX="4.074"
|
||||
android:endY="87.433"
|
||||
android:endX="16.901"
|
||||
android:type="linear">
|
||||
<item android:offset="0.127" android:color="#FFDA6727"/>
|
||||
<item android:offset="1" android:color="#FFFFC400"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M23.54,91.61c-1.69,-2.24 -8.47,-9.35 -8.3,-14c0.05,-1.49 2.43,-3.02 4.81,-1.63c0.03,0.02 -1.74,-2 -3.96,-1.21c-1.93,0.68 -2.21,2.46 -2.23,2.78c-0.1,1.01 0.18,1.98 0.45,2.72c0.56,1.4 1.27,2.76 2.29,4.39c0.76,1.24 1.65,2.5 2.79,3.99c0.63,0.81 1.27,1.55 1.92,2.25c0.44,0.48 0.89,0.94 1.35,1.39c0.11,0.1 0.25,0.16 0.39,0.16c0.12,0 0.24,-0.04 0.34,-0.11c0.21,-0.18 0.31,-0.51 0.15,-0.73z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="83.513"
|
||||
android:startX="13.586"
|
||||
android:endY="83.513"
|
||||
android:endX="23.62"
|
||||
android:type="linear">
|
||||
<item android:offset="0.127" android:color="#FFDA6727"/>
|
||||
<item android:offset="1" android:color="#FFFFC400"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M36.82,78.92c-0.72,0 -1.94,0.18 -2.59,1.17c-1.28,1.92 -0.78,5.52 0.2,8.65c0.16,0.51 0.26,0.92 0.32,1.26a0.794,0.794 0,0 1,-1.06 0.9c-2.45,-0.92 -5.17,-4.13 -7.89,-7.32l-0.41,-0.5c0.03,0.46 1.59,2.96 3.72,5.65c0.57,0.73 4.36,4.86 6.38,3.68c1.25,-0.72 -0.79,-6.32 -0.81,-7.77c-0.01,-0.79 -0.12,-1.81 0.13,-2.75c0.05,-0.24 0.2,-0.49 0.39,-0.74c1.09,-1.46 3.35,-1.52 4.28,0.05l0.03,0.04c-0.5,-1.34 -1.12,-2.32 -2.69,-2.32z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="85.763"
|
||||
android:startX="25.394"
|
||||
android:endY="85.763"
|
||||
android:endX="40.599"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FFFFC400"/>
|
||||
<item android:offset="0.873" android:color="#FFDA6727"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M88.31,84.46c1.09,-3.08 1.87,28.82 1.87,28.82c-2.92,1.23 -5.87,2.31 -8.93,3.13c-1.21,-2.73 -2.79,-7.76 -1.96,-13.46c1.34,-9.11 6.77,-12.14 9.02,-18.49z"
|
||||
android:strokeAlpha="0.66"
|
||||
android:fillAlpha="0.66">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="128.189"
|
||||
android:startX="84.62198"
|
||||
android:endY="97.296"
|
||||
android:endX="84.62198"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FFBF360C"/>
|
||||
<item android:offset="1" android:color="#33BF360C"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M87.68,81.23c0.49,-1.33 1.11,-2.32 2.68,-2.32c0.72,0 1.94,0.18 2.59,1.17c1.28,1.92 0.78,5.52 -0.2,8.65c-0.55,1.77 -0.4,2.42 -0.4,2.42c2.72,-0.17 5.88,-3.88 9.03,-7.58c1.93,-2.27 3.05,-4.36 4.93,-6.68c1.14,-1.41 2.81,-2.83 4.77,-2.11c5.37,1.99 -1.27,9.72 -2.93,11.84c-1.89,2.41 -3.76,4.72 -3.76,4.72c1.76,-0.41 2.87,-0.88 5.97,-4.06c2.65,-2.72 4.62,-6.23 6.06,-7.54c3.27,-2.97 5.27,-2 6.14,-0.02c0.81,1.84 -0.53,5.09 -1.44,6.61c-4.51,7.48 -10.27,10.41 -10.27,10.41c4.74,-0.21 8.48,-5.29 9.99,-6.83c4.38,-4.45 6.38,1.06 3.99,5.05c-1.1,1.83 -2.2,3.12 -4.21,5.19c-3.25,3.33 -6.31,4.84 -6.31,4.84s1.31,0.2 2.63,-0.49c1.78,-0.93 3.77,-2.33 4.8,-3.13c2.95,-2.27 5.76,-0.32 4.63,2.7c-1.31,3.49 -6.09,6.45 -8.98,8.6c-5.62,4.16 -12.63,7.49 -19.73,7.26c-7.9,-0.25 -13.34,-5.68 -15.48,-13.31c-1.24,-4.44 1.72,-11.5 2.47,-13.28c2.46,-5.76 2.54,-10.78 3.03,-12.11z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:gradientRadius="38.904"
|
||||
android:centerX="97.99198"
|
||||
android:centerY="93.866"
|
||||
android:type="radial">
|
||||
<item android:offset="0.33" android:color="#FFFFF176"/>
|
||||
<item android:offset="1" android:color="#FFFFC400"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M103.56,99.08c-0.14,8.38 -5.4,11.88 -9.48,12.65c1.64,-2.39 2.49,-5.31 2.39,-8.2c-0.1,-2.9 -1.22,-6.51 -2.88,-8.47c-1.66,-1.96 -1.24,-3.89 -1.24,-3.89s1.12,-0.09 1.54,-0.34c1.34,-0.82 2.88,-1.48 4.04,-2.55c0.84,-0.79 5.77,2.42 5.63,10.8z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:gradientRadius="11.727577"
|
||||
android:centerX="90.41754"
|
||||
android:centerY="102.50223"
|
||||
android:type="radial">
|
||||
<item android:offset="0" android:color="#FFFFC709"/>
|
||||
<item android:offset="1" android:color="#00FFC709"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M120.77,78.54a2.84,2.84 0,0 1,1.28 2.35c0.03,0.92 -0.24,1.82 -0.57,2.68c-0.7,1.73 -1.8,3.28 -2.93,4.77a62.626,62.626 0,0 1,-3.71 4.29c-1.3,1.39 -2.63,2.76 -3.98,4.13c1.67,-0.98 3.22,-2.16 4.65,-3.46c1.42,-1.31 2.77,-2.73 3.91,-4.31c1.17,-1.57 2.1,-3.32 2.71,-5.2c0.28,-0.94 0.48,-1.95 0.31,-2.95c-0.13,-0.99 -0.76,-1.94 -1.67,-2.3z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="95.255"
|
||||
android:startX="108.66098"
|
||||
android:endY="82.775"
|
||||
android:endX="121.35098"
|
||||
android:type="linear">
|
||||
<item android:offset="0.127" android:color="#FFB7537A"/>
|
||||
<item android:offset="1" android:color="#FFFFC400"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M126.63,102.67c-0.03,-0.28 -0.07,-0.52 -0.12,-0.73c0.06,0.23 0.11,0.47 0.12,0.73z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:gradientRadius="38.904"
|
||||
android:centerX="97.99198"
|
||||
android:centerY="93.866"
|
||||
android:type="radial">
|
||||
<item android:offset="0.33" android:color="#FFFFF176"/>
|
||||
<item android:offset="1" android:color="#FFFFC400"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M122.86,106.78c0.72,-0.69 1.21,-1.27 1.58,-1.88c0.42,-0.7 0.66,-1.44 0.68,-2.13c0.02,-0.77 -0.05,-1.34 -0.78,-1.9c-0.36,-0.28 -1.37,-0.18 -1.35,-0.19c0.01,-0.01 0.03,-0.01 0.04,-0.02c0.61,-0.24 1.18,-0.3 1.69,-0.22l-0.01,-0.01l0.01,0.01c0.86,0.14 1.52,0.69 1.78,1.5c0.05,0.21 0.09,0.45 0.12,0.73c0.01,0.43 -0.06,0.91 -0.25,1.42c-0.29,0.77 -0.76,1.52 -1.33,2.24c-0.34,0.52 -0.74,1.01 -1.11,1.48c-0.59,0.7 -1.19,1.39 -1.86,2.01a26.45,26.45 0,0 1,-4.3 3.38c-0.75,0.46 -8.17,5.01 -14.67,6.26c-0.01,-0.02 12.4,-5.14 19.76,-12.68z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="109.935"
|
||||
android:startX="126.62798"
|
||||
android:endY="109.935"
|
||||
android:endX="103.10398"
|
||||
android:type="linear">
|
||||
<item android:offset="0.127" android:color="#FFDA6727"/>
|
||||
<item android:offset="1" android:color="#FFFFC400"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M113.92,105.53c0.07,0 0.15,-0.01 0.21,-0.04c1.58,-0.66 3.15,-1.6 4.81,-2.88a27.08,27.08 0,0 0,4.01 -3.91c0.64,-0.74 1.21,-1.55 1.67,-2.24l0.14,-0.22c0.47,-0.73 0.95,-1.48 1.25,-2.36c0.39,-1.04 0.49,-2.11 0.27,-3.1c-0.25,-1.19 -0.93,-2 -1.88,-2.41c-1.36,-0.59 -2.88,0.91 -2.79,0.87c1.05,-0.47 1.97,-0.21 2.21,-0.05c0.55,0.38 0.91,1.04 0.98,1.81c0.05,0.69 -0.11,1.45 -0.46,2.19c-0.3,0.65 -0.72,1.28 -1.37,2.04c-0.56,0.72 -1.14,1.38 -1.71,2.03a57.36,57.36 0,0 1,-3.68 3.77c-1.07,0.99 -2.49,2.28 -4.02,3.5c-0.22,0.17 -0.28,0.48 -0.13,0.72c0.11,0.18 0.3,0.28 0.49,0.28z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="96.876"
|
||||
android:startX="126.39898"
|
||||
android:endY="96.876"
|
||||
android:endX="113.361984"
|
||||
android:type="linear">
|
||||
<item android:offset="0.127" android:color="#FFDA6727"/>
|
||||
<item android:offset="1" android:color="#FFFFC400"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M110.4,96.91c-0.19,-0.21 -0.15,-0.34 0.05,-0.54c1.49,-1.51 2.79,-2.86 3.97,-4.12a61.13,61.13 0,0 0,3.67 -4.24c1.08,-1.42 2.18,-2.96 2.86,-4.63c0.26,-0.68 0.56,-1.58 0.54,-2.47c0,-0.4 -0.11,-0.77 -0.29,-1.1c-0.17,-0.31 -0.42,-0.58 -0.73,-0.78c-0.01,0 -0.01,-0.01 -0.01,-0.01c-0.51,-0.31 -1.34,-0.54 -2.67,-0.31c2.33,-1.57 4.08,-0.68 4.82,1.01c0.81,1.84 -0.57,5.13 -1.49,6.65c-4.51,7.48 -10.02,10.47 -10.02,10.47c-0.26,0.16 -0.57,0.21 -0.7,0.07z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="87.433"
|
||||
android:startX="123.10298"
|
||||
android:endY="87.433"
|
||||
android:endX="110.275986"
|
||||
android:type="linear">
|
||||
<item android:offset="0.127" android:color="#FFDA6727"/>
|
||||
<item android:offset="1" android:color="#FFFFC400"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M103.64,91.61c1.69,-2.24 8.47,-9.35 8.3,-14c-0.05,-1.49 -2.43,-3.02 -4.81,-1.63c-0.03,0.02 1.74,-2 3.96,-1.21c1.93,0.68 2.21,2.46 2.23,2.78c0.1,1.01 -0.18,1.98 -0.45,2.72c-0.56,1.4 -1.27,2.76 -2.29,4.39c-0.76,1.24 -1.65,2.5 -2.79,3.99c-0.63,0.81 -1.27,1.55 -1.92,2.25c-0.44,0.48 -0.89,0.94 -1.35,1.39a0.59,0.59 0,0 1,-0.39 0.16c-0.12,0 -0.24,-0.04 -0.34,-0.11c-0.22,-0.18 -0.32,-0.51 -0.15,-0.73z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="83.513"
|
||||
android:startX="113.59198"
|
||||
android:endY="83.513"
|
||||
android:endX="103.55798"
|
||||
android:type="linear">
|
||||
<item android:offset="0.127" android:color="#FFDA6727"/>
|
||||
<item android:offset="1" android:color="#FFFFC400"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M90.36,78.92c0.72,0 1.94,0.18 2.59,1.17c1.28,1.92 0.78,5.52 -0.2,8.65c-0.16,0.51 -0.26,0.92 -0.32,1.26a0.794,0.794 0,0 0,1.06 0.9c2.45,-0.92 5.17,-4.13 7.89,-7.32l0.41,-0.5c-0.03,0.46 -1.59,2.96 -3.72,5.65c-0.57,0.73 -4.36,4.86 -6.38,3.68c-1.25,-0.72 0.79,-6.32 0.81,-7.77c0.01,-0.79 0.12,-1.81 -0.13,-2.75c-0.05,-0.24 -0.2,-0.49 -0.39,-0.74c-1.09,-1.46 -3.35,-1.52 -4.28,0.05l-0.03,0.04c0.5,-1.34 1.11,-2.32 2.69,-2.32z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startY="85.763"
|
||||
android:startX="101.78398"
|
||||
android:endY="85.763"
|
||||
android:endX="86.57798"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FFFFC400"/>
|
||||
<item android:offset="0.873" android:color="#FFDA6727"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M0,0h128v128h-128z"
|
||||
android:fillColor="#00000000"/>
|
||||
</vector>
|
BIN
app/src/main/res/drawable/games_achievements.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
app/src/main/res/drawable/games_controller.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/drawable/games_leaderboards.png
Normal file
After Width: | Height: | Size: 11 KiB |
|
@ -31,7 +31,6 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:includeFontPadding="false"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout 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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
@ -10,7 +11,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:elevation="0dp"
|
||||
android:minHeight="30dp"
|
||||
android:theme="@style/AppTheme.AppBarOverlay"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:targetApi="lollipop" />
|
||||
|
||||
<LinearLayout
|
||||
|
@ -29,9 +30,6 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:drawableStart="@drawable/timer"
|
||||
android:drawableLeft="@drawable/timer"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:includeFontPadding="false"
|
||||
|
@ -41,6 +39,8 @@
|
|||
android:textSize="@dimen/text_size"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
app:drawableLeftCompat="@drawable/timer"
|
||||
app:drawableStartCompat="@drawable/timer"
|
||||
tools:text="10:00"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
@ -49,8 +49,6 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:drawableStart="@drawable/mine"
|
||||
android:drawableLeft="@drawable/mine"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:includeFontPadding="false"
|
||||
|
@ -59,6 +57,7 @@
|
|||
android:textSize="@dimen/text_size"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
app:drawableStartCompat="@drawable/mine"
|
||||
tools:text="99"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
|
|
|
@ -13,13 +13,14 @@
|
|||
android:layout_height="64dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:focusable="false"
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_marginTop="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/emoji_smiling_face_with_sunglasses" />
|
||||
tools:src="@drawable/emoji_smiling_face_with_sunglasses"
|
||||
tools:ignore="KeyboardInaccessibleWidget" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
|
|
47
app/src/main/res/layout/dialog_payments.xml
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/emoji"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/emoji_hugging_face"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/supportText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/support"
|
||||
android:layout_marginTop="24dp"
|
||||
android:textStyle="bold"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/emoji"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/supportDescription"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/support_description"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:gravity="center"
|
||||
android:textSize="16sp"
|
||||
android:paddingBottom="8dp"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/supportText"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -9,11 +9,9 @@
|
|||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:paddingLeft="32dp"
|
||||
android:paddingRight="48dp"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingEnd="48dp"
|
||||
android:paddingVertical="24dp">
|
||||
android:paddingVertical="12dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatRadioButton
|
||||
android:id="@+id/radio"
|
||||
|
@ -35,15 +33,6 @@
|
|||
tools:text="">
|
||||
|
||||
<TableRow>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:textStyle="bold"
|
||||
tools:text="Title" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow android:paddingVertical="4dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/firstAction"
|
||||
tools:text="First Action" />
|
||||
|
@ -62,7 +51,6 @@
|
|||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/secondAction"
|
||||
tools:text="Long Tap" />
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_constraintStart_toEndOf="@id/badge"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
@ -35,7 +34,6 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_constraintStart_toEndOf="@id/badge"
|
||||
app:layout_constraintTop_toBottomOf="@id/difficulty"
|
||||
|
@ -46,7 +44,6 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="-"
|
||||
app:layout_constraintStart_toEndOf="@id/minefieldSize"
|
||||
|
@ -58,7 +55,6 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
app:layout_constraintStart_toEndOf="@id/dash"
|
||||
app:layout_constraintTop_toBottomOf="@id/difficulty"
|
||||
|
|
33
app/src/main/res/layout/view_play_games_button.xml
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout 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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:drawablePadding="10dp"
|
||||
app:tint="@android:color/tab_indicator_text"
|
||||
app:tintMode="src_in"
|
||||
tools:src="@drawable/games_achievements" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginStart="8dp"
|
||||
tools:text="Text" />
|
||||
|
||||
</LinearLayout>
|
|
@ -11,7 +11,6 @@
|
|||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:columnCount="3"
|
||||
android:background="#FFFF0000"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
|
|
|
@ -71,6 +71,14 @@
|
|||
android:title="@string/settings" />
|
||||
</group>
|
||||
|
||||
<group android:id="@+id/play_games_group">
|
||||
<item
|
||||
android:id="@+id/play_games"
|
||||
android:checkable="false"
|
||||
android:icon="@drawable/games_controller"
|
||||
android:title="@string/google_play_games" />
|
||||
</group>
|
||||
|
||||
<group
|
||||
android:id="@+id/install_group"
|
||||
android:visible="false"
|
||||
|
|
|
@ -3,11 +3,10 @@ package dev.lucasnlm.antimine
|
|||
import android.os.Build
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.test.core.app.launchActivity
|
||||
import dagger.hilt.android.testing.HiltAndroidRule
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import dagger.hilt.android.testing.HiltTestApplication
|
||||
import dagger.hilt.android.testing.UninstallModules
|
||||
import dev.lucasnlm.antimine.common.level.di.LevelModule
|
||||
import dev.lucasnlm.antimine.di.AppModule
|
||||
import dev.lucasnlm.antimine.di.TestCommonModule
|
||||
import dev.lucasnlm.antimine.di.TestLevelModule
|
||||
import dev.lucasnlm.antimine.di.ViewModelModule
|
||||
import dev.lucasnlm.antimine.level.view.EndGameDialogFragment
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
|
@ -15,23 +14,24 @@ import org.junit.Ignore
|
|||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.koin.test.KoinTestRule
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.annotation.LooperMode
|
||||
import org.robolectric.shadows.ShadowLooper
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@HiltAndroidTest
|
||||
@UninstallModules(LevelModule::class)
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [Build.VERSION_CODES.P], application = HiltTestApplication::class)
|
||||
@Config(sdk = [Build.VERSION_CODES.P], application = TestApplication::class)
|
||||
@LooperMode(LooperMode.Mode.PAUSED)
|
||||
class GameActivityTest {
|
||||
@get:Rule
|
||||
var rule = HiltAndroidRule(this)
|
||||
val koinTestRule = KoinTestRule.create {
|
||||
modules(AppModule, TestLevelModule, TestCommonModule, ViewModelModule)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Dagger hilt issue")
|
||||
@Ignore("Disabled until fix touch on tests")
|
||||
fun testShowGameOverWhenTapAMine() {
|
||||
launchActivity<GameActivity>().onActivity { activity ->
|
||||
ShadowLooper.runUiThreadTasks()
|
||||
|
@ -57,7 +57,7 @@ class GameActivityTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Dagger hilt issue")
|
||||
@Ignore("Disabled until fix touch on tests")
|
||||
fun testShowVictoryWhenTapAllSafeAreas() {
|
||||
launchActivity<GameActivity>().onActivity { activity ->
|
||||
ShadowLooper.runUiThreadTasks()
|
||||
|
@ -70,7 +70,7 @@ class GameActivityTest {
|
|||
ShadowLooper.runUiThreadTasks()
|
||||
|
||||
// Tap on safe places
|
||||
activity.viewModel.field
|
||||
activity.gameViewModel.field
|
||||
.value!!
|
||||
.filter { !it.hasMine && it.isCovered }
|
||||
.forEach {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package dev.lucasnlm.antimine
|
||||
|
||||
import android.app.Application
|
||||
|
||||
class TestApplication : Application()
|
|
@ -0,0 +1,17 @@
|
|||
package dev.lucasnlm.antimine.about
|
||||
|
||||
import dev.lucasnlm.antimine.IntentViewModelTest
|
||||
import dev.lucasnlm.antimine.about.viewmodel.AboutViewModel
|
||||
import io.mockk.mockk
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class AboutViewModelTest : IntentViewModelTest() {
|
||||
@Test
|
||||
fun testLoad() {
|
||||
val viewModel = AboutViewModel(mockk())
|
||||
val state = viewModel.singleState()
|
||||
assertTrue(state.licenses.isNotEmpty())
|
||||
assertTrue(state.translators.isNotEmpty())
|
||||
}
|
||||
}
|
|
@ -1,22 +1,19 @@
|
|||
package dev.lucasnlm.antimine.control.viewmodel
|
||||
package dev.lucasnlm.antimine.control
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import dev.lucasnlm.antimine.IntentViewModelTest
|
||||
import dev.lucasnlm.antimine.control.viewmodel.ControlEvent
|
||||
import dev.lucasnlm.antimine.control.viewmodel.ControlViewModel
|
||||
import dev.lucasnlm.antimine.core.control.ControlStyle
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class ControlViewModelTest : IntentViewModelTest() {
|
||||
@get:Rule
|
||||
val rule = InstantTaskExecutorRule()
|
||||
|
||||
private fun ControlViewModel.selectedControlStyle() = singleState().let {
|
||||
it.gameControls[it.selectedId].controlStyle
|
||||
it.gameControls[it.selectedIndex].controlStyle
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -36,7 +33,11 @@ class ControlViewModelTest : IntentViewModelTest() {
|
|||
}
|
||||
|
||||
val viewModel = ControlViewModel(preferenceRepository)
|
||||
viewModel.sendEvent(ControlEvent.SelectControlStyle(ControlStyle.FastFlag))
|
||||
viewModel.sendEvent(
|
||||
ControlEvent.SelectControlStyle(
|
||||
ControlStyle.FastFlag
|
||||
)
|
||||
)
|
||||
assertEquals(ControlStyle.FastFlag, viewModel.selectedControlStyle())
|
||||
verify { preferenceRepository.useControlStyle(ControlStyle.FastFlag) }
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package dev.lucasnlm.antimine.custom
|
||||
|
||||
import dev.lucasnlm.antimine.IntentViewModelTest
|
||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import dev.lucasnlm.antimine.custom.viewmodel.CreateGameViewModel
|
||||
import dev.lucasnlm.antimine.custom.viewmodel.CustomEvent
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class CreateGameViewModelTest : IntentViewModelTest() {
|
||||
@Test
|
||||
fun testInitialValue() {
|
||||
val preferenceRepository: IPreferencesRepository = mockk {
|
||||
every { customGameMode() } returns Minefield(10, 12, 9)
|
||||
}
|
||||
|
||||
val result = CreateGameViewModel(preferenceRepository).singleState()
|
||||
|
||||
assertEquals(10, result.width)
|
||||
assertEquals(12, result.height)
|
||||
assertEquals(9, result.mines)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetNewCustomValues() {
|
||||
val preferenceRepository: IPreferencesRepository = mockk {
|
||||
every { customGameMode() } returns Minefield(10, 12, 9)
|
||||
every { updateCustomGameMode(any()) } returns Unit
|
||||
}
|
||||
|
||||
val result = CreateGameViewModel(preferenceRepository).run {
|
||||
sendEvent(CustomEvent.UpdateCustomGameEvent(Minefield(9, 8, 5)))
|
||||
singleState()
|
||||
}
|
||||
|
||||
assertEquals(9, result.width)
|
||||
assertEquals(8, result.height)
|
||||
assertEquals(5, result.mines)
|
||||
}
|
||||
}
|
58
app/src/test/java/dev/lucasnlm/antimine/di/TestAppModule.kt
Normal file
|
@ -0,0 +1,58 @@
|
|||
package dev.lucasnlm.antimine.di
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import dev.lucasnlm.antimine.core.analytics.DebugAnalyticsManager
|
||||
import dev.lucasnlm.antimine.core.analytics.IAnalyticsManager
|
||||
import dev.lucasnlm.antimine.share.ShareManager
|
||||
import dev.lucasnlm.external.IBillingManager
|
||||
import dev.lucasnlm.external.IInstantAppManager
|
||||
import dev.lucasnlm.external.IPlayGamesManager
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
|
||||
val AppModule = module {
|
||||
single {
|
||||
object : IInstantAppManager {
|
||||
override fun isEnabled(context: Context): Boolean = false
|
||||
|
||||
override fun showInstallPrompt(
|
||||
activity: Activity,
|
||||
intent: Intent?,
|
||||
requestCode: Int,
|
||||
referrer: String?
|
||||
): Boolean = false
|
||||
}
|
||||
} bind IInstantAppManager::class
|
||||
|
||||
single {
|
||||
object : IBillingManager {
|
||||
override fun start() { }
|
||||
|
||||
override suspend fun charge(activity: Activity) { }
|
||||
}
|
||||
} bind IBillingManager::class
|
||||
|
||||
single { object : IPlayGamesManager {
|
||||
override fun hasGooglePlayGames(): Boolean = false
|
||||
|
||||
override fun silentLogin(activity: Activity) { }
|
||||
|
||||
override fun getLoginIntent(): Intent? = null
|
||||
|
||||
override fun handleLoginResult(data: Intent?) { }
|
||||
|
||||
override fun isLogged(): Boolean = false
|
||||
|
||||
override fun openAchievements(activity: Activity) { }
|
||||
|
||||
override fun openLeaderboards(activity: Activity) { }
|
||||
} } bind IPlayGamesManager::class
|
||||
|
||||
single { ShareManager(get()) }
|
||||
|
||||
single {
|
||||
DebugAnalyticsManager()
|
||||
} bind IAnalyticsManager::class
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package dev.lucasnlm.antimine.di
|
||||
|
||||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import dev.lucasnlm.antimine.core.sound.ISoundManager
|
||||
import dev.lucasnlm.antimine.core.themes.model.AppTheme
|
||||
import dev.lucasnlm.antimine.core.themes.repository.IThemeRepository
|
||||
import dev.lucasnlm.antimine.core.themes.repository.Themes.LightTheme
|
||||
import dev.lucasnlm.antimine.mocks.FixedDimensionRepository
|
||||
import dev.lucasnlm.antimine.mocks.MockPreferencesRepository
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
|
||||
val TestCommonModule = module {
|
||||
single { FixedDimensionRepository() } bind IDimensionRepository::class
|
||||
|
||||
single { MockPreferencesRepository() } bind IPreferencesRepository::class
|
||||
|
||||
single { object : ISoundManager {
|
||||
override fun play(soundId: Int) { }
|
||||
} } bind ISoundManager::class
|
||||
|
||||
single { object : IThemeRepository {
|
||||
override fun getCustomTheme(): AppTheme? = null
|
||||
|
||||
override fun getTheme(): AppTheme = LightTheme
|
||||
|
||||
override fun getAllThemes(): List<AppTheme> = listOf(LightTheme)
|
||||
|
||||
override fun setTheme(theme: AppTheme) { }
|
||||
} } bind IThemeRepository::class
|
||||
}
|
|
@ -1,11 +1,5 @@
|
|||
package dev.lucasnlm.antimine.di
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dev.lucasnlm.antimine.common.level.models.Event
|
||||
import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||
import dev.lucasnlm.antimine.common.level.repository.IStatsRepository
|
||||
|
@ -15,25 +9,27 @@ import dev.lucasnlm.antimine.common.level.utils.Clock
|
|||
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackManager
|
||||
import dev.lucasnlm.antimine.mocks.DisabledHapticFeedbackManager
|
||||
import dev.lucasnlm.antimine.mocks.FixedMinefieldRepository
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
class TestLevelModule {
|
||||
@Provides
|
||||
fun provideGameEventObserver(): MutableLiveData<Event> = MutableLiveData()
|
||||
val TestLevelModule = module {
|
||||
single {
|
||||
Clock()
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideClock(): Clock = Clock()
|
||||
single {
|
||||
MemorySavesRepository()
|
||||
} bind ISavesRepository::class
|
||||
|
||||
@Provides
|
||||
fun provideSavesRepository(): ISavesRepository = MemorySavesRepository()
|
||||
single {
|
||||
MemoryStatsRepository()
|
||||
} bind IStatsRepository::class
|
||||
|
||||
@Provides
|
||||
fun provideStatsRepository(): IStatsRepository = MemoryStatsRepository()
|
||||
single {
|
||||
FixedMinefieldRepository()
|
||||
} bind IMinefieldRepository::class
|
||||
|
||||
@Provides
|
||||
fun provideMinefieldRepository(): IMinefieldRepository = FixedMinefieldRepository()
|
||||
|
||||
@Provides
|
||||
fun provideHapticFeedbackInteractor(): IHapticFeedbackManager = DisabledHapticFeedbackManager()
|
||||
single {
|
||||
DisabledHapticFeedbackManager()
|
||||
} bind IHapticFeedbackManager::class
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package dev.lucasnlm.antimine.history
|
||||
|
||||
import dev.lucasnlm.antimine.IntentViewModelTest
|
||||
import dev.lucasnlm.antimine.common.level.database.models.FirstOpen
|
||||
import dev.lucasnlm.antimine.common.level.database.models.Save
|
||||
import dev.lucasnlm.antimine.common.level.database.models.SaveStatus
|
||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
|
||||
import dev.lucasnlm.antimine.history.viewmodel.HistoryEvent
|
||||
import dev.lucasnlm.antimine.history.viewmodel.HistoryState
|
||||
import dev.lucasnlm.antimine.history.viewmodel.HistoryViewModel
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class HistoryViewModelTest : IntentViewModelTest() {
|
||||
private val fakeMinefield = Minefield(9, 9, 9)
|
||||
private val allSaves = listOf(
|
||||
Save(
|
||||
0, 1, 0L, 100L, fakeMinefield,
|
||||
Difficulty.Beginner, FirstOpen.Unknown, SaveStatus.ON_GOING, listOf()
|
||||
),
|
||||
Save(
|
||||
1, 2, 0L, 100L, fakeMinefield,
|
||||
Difficulty.Beginner, FirstOpen.Unknown, SaveStatus.ON_GOING, listOf()
|
||||
),
|
||||
Save(
|
||||
2, 3, 0L, 100L, fakeMinefield,
|
||||
Difficulty.Beginner, FirstOpen.Unknown, SaveStatus.ON_GOING, listOf()
|
||||
)
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testInitialValue() {
|
||||
val viewModel = HistoryViewModel(mockk(), mockk())
|
||||
assertEquals(HistoryState(listOf()), viewModel.singleState())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLoadHistory() {
|
||||
val savesRepository = mockk<ISavesRepository> {
|
||||
coEvery { getAllSaves() } returns allSaves
|
||||
}
|
||||
|
||||
val state = HistoryViewModel(mockk(), savesRepository).run {
|
||||
sendEvent(HistoryEvent.LoadAllSaves)
|
||||
singleState()
|
||||
}
|
||||
assertEquals(HistoryState(allSaves.sortedByDescending { it.uid }), state)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package dev.lucasnlm.antimine.mocks
|
||||
|
||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
import dev.lucasnlm.antimine.core.control.ControlStyle
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
|
||||
class MockPreferencesRepository : IPreferencesRepository {
|
||||
var customMinefield = Minefield(9, 9, 9)
|
||||
|
||||
override fun customGameMode(): Minefield = customMinefield
|
||||
|
||||
override fun updateCustomGameMode(minefield: Minefield) {
|
||||
customMinefield = minefield
|
||||
}
|
||||
|
||||
override fun controlStyle(): ControlStyle = ControlStyle.Standard
|
||||
|
||||
override fun useControlStyle(controlStyle: ControlStyle) { }
|
||||
|
||||
override fun isFirstUse(): Boolean = false
|
||||
|
||||
override fun completeFirstUse() { }
|
||||
|
||||
override fun customLongPressTimeout(): Long = 400L
|
||||
|
||||
override fun themeId(): Long = 1L
|
||||
|
||||
override fun useTheme(themeId: Long) { }
|
||||
|
||||
override fun updateStatsBase(statsBase: Int) { }
|
||||
|
||||
override fun getStatsBase(): Int = 0
|
||||
|
||||
override fun getUseCount(): Int = 10
|
||||
|
||||
override fun incrementUseCount() { }
|
||||
|
||||
override fun incrementProgressiveValue() { }
|
||||
|
||||
override fun decrementProgressiveValue() { }
|
||||
|
||||
override fun getProgressiveValue(): Int = 0
|
||||
|
||||
override fun isRequestRatingEnabled(): Boolean = false
|
||||
|
||||
override fun disableRequestRating() { }
|
||||
|
||||
override fun useFlagAssistant(): Boolean = false
|
||||
|
||||
override fun useHapticFeedback(): Boolean = true
|
||||
|
||||
override fun areaSizeMultiplier(): Int = 50
|
||||
|
||||
override fun useAnimations(): Boolean = false
|
||||
|
||||
override fun useQuestionMark(): Boolean = false
|
||||
|
||||
override fun isSoundEffectsEnabled(): Boolean = false
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package dev.lucasnlm.antimine.theme
|
||||
|
||||
import dev.lucasnlm.antimine.IntentViewModelTest
|
||||
import dev.lucasnlm.antimine.common.R
|
||||
import dev.lucasnlm.antimine.core.themes.model.AppTheme
|
||||
import dev.lucasnlm.antimine.core.themes.model.AreaPalette
|
||||
import dev.lucasnlm.antimine.core.themes.model.Assets
|
||||
import dev.lucasnlm.antimine.core.themes.repository.IThemeRepository
|
||||
import dev.lucasnlm.antimine.theme.viewmodel.ThemeEvent
|
||||
import dev.lucasnlm.antimine.theme.viewmodel.ThemeState
|
||||
import dev.lucasnlm.antimine.theme.viewmodel.ThemeViewModel
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class ThemeViewModelTest : IntentViewModelTest() {
|
||||
private val lightTheme = AppTheme(
|
||||
id = 1L,
|
||||
theme = R.style.CustomLightTheme,
|
||||
themeNoActionBar = R.style.CustomLightTheme_NoActionBar,
|
||||
palette = AreaPalette(
|
||||
border = 0x424242,
|
||||
background = 0xFFFFFF,
|
||||
covered = 0x424242,
|
||||
coveredOdd = 0x424242,
|
||||
uncovered = 0xd5d2cc,
|
||||
uncoveredOdd = 0xd5d2cc,
|
||||
minesAround1 = 0x527F8D,
|
||||
minesAround2 = 0x2B8D43,
|
||||
minesAround3 = 0xE65100,
|
||||
minesAround4 = 0x20A5f7,
|
||||
minesAround5 = 0xED1C24,
|
||||
minesAround6 = 0xFFC107,
|
||||
minesAround7 = 0x66126B,
|
||||
minesAround8 = 0x000000,
|
||||
highlight = 0x212121,
|
||||
focus = 0xD32F2F
|
||||
),
|
||||
assets = Assets(
|
||||
wrongFlag = R.drawable.red_flag,
|
||||
flag = R.drawable.flag,
|
||||
questionMark = R.drawable.question,
|
||||
toolbarMine = R.drawable.mine,
|
||||
mine = R.drawable.mine,
|
||||
mineExploded = R.drawable.mine_exploded_red,
|
||||
mineLow = R.drawable.mine_low
|
||||
)
|
||||
)
|
||||
|
||||
private val darkTheme = AppTheme(
|
||||
id = 2L,
|
||||
theme = R.style.CustomDarkTheme,
|
||||
themeNoActionBar = R.style.CustomDarkTheme_NoActionBar,
|
||||
palette = AreaPalette(
|
||||
border = 0x171717,
|
||||
background = 0x212121,
|
||||
covered = 0x171717,
|
||||
coveredOdd = 0x171717,
|
||||
uncovered = 0x424242,
|
||||
uncoveredOdd = 0x424242,
|
||||
minesAround1 = 0xd5d2cc,
|
||||
minesAround2 = 0xd5d2cc,
|
||||
minesAround3 = 0xd5d2cc,
|
||||
minesAround4 = 0xd5d2cc,
|
||||
minesAround5 = 0xd5d2cc,
|
||||
minesAround6 = 0xd5d2cc,
|
||||
minesAround7 = 0xd5d2cc,
|
||||
minesAround8 = 0xd5d2cc,
|
||||
highlight = 0xFFFFFF,
|
||||
focus = 0xFFFFFF
|
||||
),
|
||||
assets = Assets(
|
||||
wrongFlag = R.drawable.flag,
|
||||
flag = R.drawable.flag,
|
||||
questionMark = R.drawable.question,
|
||||
toolbarMine = R.drawable.mine_low,
|
||||
mine = R.drawable.mine,
|
||||
mineExploded = R.drawable.mine_exploded_white,
|
||||
mineLow = R.drawable.mine_low
|
||||
)
|
||||
)
|
||||
|
||||
private val gardenTheme = AppTheme(
|
||||
id = 3L,
|
||||
theme = R.style.CustomGardenTheme,
|
||||
themeNoActionBar = R.style.CustomGardenTheme_NoActionBar,
|
||||
palette = AreaPalette(
|
||||
border = 0x171717,
|
||||
background = 0xefebe9,
|
||||
covered = 0x689f38,
|
||||
coveredOdd = 0x558b2f,
|
||||
uncovered = 0xefebe9,
|
||||
uncoveredOdd = 0xd7ccc8,
|
||||
minesAround1 = 0x527F8D,
|
||||
minesAround2 = 0x2B8D43,
|
||||
minesAround3 = 0xE65100,
|
||||
minesAround4 = 0x20A5f7,
|
||||
minesAround5 = 0xED1C24,
|
||||
minesAround6 = 0xFFC107,
|
||||
minesAround7 = 0x66126B,
|
||||
minesAround8 = 0x000000,
|
||||
highlight = 0xFFFFFF,
|
||||
focus = 0xFFFFFF
|
||||
),
|
||||
assets = Assets(
|
||||
wrongFlag = R.drawable.flag,
|
||||
flag = R.drawable.flag,
|
||||
questionMark = R.drawable.question,
|
||||
toolbarMine = R.drawable.mine_low,
|
||||
mine = R.drawable.mine,
|
||||
mineExploded = R.drawable.mine_exploded_white,
|
||||
mineLow = R.drawable.mine_low
|
||||
)
|
||||
)
|
||||
|
||||
private val allThemes = listOf(
|
||||
lightTheme, darkTheme, gardenTheme
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testInitialValue() {
|
||||
val themeRepository = mockk<IThemeRepository> {
|
||||
every { getAllThemes() } returns allThemes
|
||||
every { getTheme() } returns gardenTheme
|
||||
}
|
||||
|
||||
val viewModel = ThemeViewModel(themeRepository)
|
||||
assertEquals(ThemeState(gardenTheme, allThemes), viewModel.singleState())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testChangeValue() {
|
||||
val themeRepository = mockk<IThemeRepository> {
|
||||
every { getAllThemes() } returns allThemes
|
||||
every { getTheme() } returns gardenTheme
|
||||
every { setTheme(any()) } returns Unit
|
||||
}
|
||||
|
||||
val state = ThemeViewModel(themeRepository).run {
|
||||
sendEvent(ThemeEvent.ChangeTheme(darkTheme))
|
||||
singleState()
|
||||
}
|
||||
assertEquals(ThemeState(darkTheme, allThemes), state)
|
||||
|
||||
verify { themeRepository.setTheme(darkTheme) }
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
sdk=28
|
||||
application=dagger.hilt.android.testing.HiltTestApplication
|
|
@ -6,9 +6,12 @@ buildscript {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28.1-alpha'
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72'
|
||||
|
||||
if (System.getenv('IS_GOOGLE_BUILD')) {
|
||||
classpath 'com.google.gms:google-services:4.3.3'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,16 +2,15 @@ apply plugin: 'com.android.library'
|
|||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'dagger.hilt.android.plugin'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
|
||||
defaultConfig {
|
||||
// versionCode and versionName must be hardcoded to support F-droid
|
||||
versionCode 800041
|
||||
versionName '8.0.4'
|
||||
minSdkVersion 16
|
||||
versionCode 800051
|
||||
versionName '8.0.5'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
}
|
||||
|
@ -33,14 +32,11 @@ kapt {
|
|||
correctErrorTypes true
|
||||
}
|
||||
|
||||
hilt {
|
||||
enableTransformForLocalTests true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Dependencies must be hardcoded to support F-droid
|
||||
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation project(':external')
|
||||
|
||||
// AndroidX
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
|
@ -56,13 +52,10 @@ dependencies {
|
|||
api 'android.arch.lifecycle:extensions:1.1.1'
|
||||
implementation 'android.arch.lifecycle:viewmodel:1.1.1'
|
||||
|
||||
// Dagger
|
||||
implementation 'com.google.dagger:hilt-android:2.28.1-alpha'
|
||||
kapt 'com.google.dagger:hilt-android-compiler:2.28.1-alpha'
|
||||
testImplementation 'com.google.dagger:hilt-android-testing:2.28.1-alpha'
|
||||
kaptTest 'com.google.dagger:hilt-android-compiler:2.28.1-alpha'
|
||||
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01"
|
||||
kapt "androidx.hilt:hilt-compiler:1.0.0-alpha01"
|
||||
// Koin
|
||||
implementation 'org.koin:koin-android:2.1.6'
|
||||
implementation 'org.koin:koin-androidx-viewmodel:2.1.6'
|
||||
testImplementation 'org.koin:koin-test:2.1.6'
|
||||
|
||||
// Room
|
||||
api 'androidx.room:room-runtime:2.2.5'
|
||||
|
|
Before Width: | Height: | Size: 664 B After Width: | Height: | Size: 695 B |
Before Width: | Height: | Size: 799 B After Width: | Height: | Size: 807 B |
Before Width: | Height: | Size: 864 B After Width: | Height: | Size: 925 B |
Before Width: | Height: | Size: 969 B After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 647 B After Width: | Height: | Size: 689 B |
Before Width: | Height: | Size: 799 B After Width: | Height: | Size: 807 B |
Before Width: | Height: | Size: 664 B After Width: | Height: | Size: 695 B |
Before Width: | Height: | Size: 799 B After Width: | Height: | Size: 807 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 865 B After Width: | Height: | Size: 936 B |
Before Width: | Height: | Size: 1,001 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.2 KiB |