diff --git a/app/build.gradle b/app/build.gradle index 4f4c144c..0a23d357 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { defaultConfig { // versionCode and versionName must be hardcoded to support F-droid - versionCode 702031 - versionName '7.2.3' + versionCode 702041 + versionName '7.2.4' minSdkVersion 16 targetSdkVersion 30 multiDexEnabled true @@ -88,7 +88,7 @@ dependencies { implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.activity:activity-ktx:1.1.0' - implementation "androidx.fragment:fragment-ktx:1.2.5" + implementation 'androidx.fragment:fragment-ktx:1.2.5' // Constraint implementation 'androidx.constraintlayout:constraintlayout:1.1.3' diff --git a/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt b/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt index 31fdab7e..e6e9e36c 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/GameActivity.kt @@ -105,6 +105,22 @@ class GameActivity : AppCompatActivity(), DialogInterface.OnDismissListener { } ) + retryObserver.observe( + this@GameActivity, + Observer { + GlobalScope.launch { + viewModel.retryGame(currentSaveId.toInt()) + } + } + ) + + shareObserver.observe( + this@GameActivity, + Observer { + shareCurrentGame() + } + ) + elapsedTimeSeconds.observe( this@GameActivity, Observer { @@ -445,8 +461,7 @@ class GameActivity : AppCompatActivity(), DialogInterface.OnDismissListener { victory, score?.rightMines ?: 0, score?.totalMines ?: 0, - currentGameStatus.time, - currentSaveId + currentGameStatus.time ).apply { showAllowingStateLoss(supportFragmentManager, EndGameDialogFragment.TAG) } @@ -516,6 +531,7 @@ class GameActivity : AppCompatActivity(), DialogInterface.OnDismissListener { ) } Event.GameOver -> { + val isResuming = (status == Status.PreGame) val score = Score( rightMines, totalMines, @@ -527,7 +543,7 @@ class GameActivity : AppCompatActivity(), DialogInterface.OnDismissListener { viewModel.stopClock() GlobalScope.launch(context = Dispatchers.Main) { - viewModel.gameOver() + viewModel.gameOver(isResuming) waitAndShowEndGameDialog( victory = false, await = true diff --git a/app/src/main/java/dev/lucasnlm/antimine/TvGameActivity.kt b/app/src/main/java/dev/lucasnlm/antimine/TvGameActivity.kt index 673c2ccb..fe18b45e 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/TvGameActivity.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/TvGameActivity.kt @@ -309,7 +309,7 @@ class TvGameActivity : AppCompatActivity() { viewModel.stopClock() GlobalScope.launch(context = Dispatchers.Main) { - viewModel.gameOver() + viewModel.gameOver(false) waitAndShowGameOverConfirmNewGame() } } diff --git a/app/src/main/java/dev/lucasnlm/antimine/about/viewmodel/AboutViewModel.kt b/app/src/main/java/dev/lucasnlm/antimine/about/viewmodel/AboutViewModel.kt index b31726ab..9c9eedd3 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/about/viewmodel/AboutViewModel.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/about/viewmodel/AboutViewModel.kt @@ -12,7 +12,8 @@ class AboutViewModel : ViewModel() { fun getTranslatorsList() = mapOf( "Arabic" to sequenceOf("Ahmad Alkurbi"), - "Chinese Simplified" to sequenceOf("linsui"), + "Chinese Simplified" to sequenceOf("linsui", "yilinzhao2020"), + "Catalan" to sequenceOf("dmanye", "Archison"), "Czech" to sequenceOf("novas78@xda"), "Dutch" to sequenceOf("Max Pietersma"), "English" to sequenceOf("miguelsouza2212"), diff --git a/app/src/main/java/dev/lucasnlm/antimine/level/view/EndGameDialogFragment.kt b/app/src/main/java/dev/lucasnlm/antimine/level/view/EndGameDialogFragment.kt index 63954561..cadb8ac6 100644 --- a/app/src/main/java/dev/lucasnlm/antimine/level/view/EndGameDialogFragment.kt +++ b/app/src/main/java/dev/lucasnlm/antimine/level/view/EndGameDialogFragment.kt @@ -14,9 +14,6 @@ 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.EngGameDialogViewModel -import dev.lucasnlm.antimine.share.viewmodel.ShareViewModel -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch import javax.inject.Inject @AndroidEntryPoint @@ -26,14 +23,12 @@ class EndGameDialogFragment : AppCompatDialogFragment() { private val endGameViewModel by activityViewModels() private val viewModel by activityViewModels() - private val shareViewModel by activityViewModels() private var hasValidData = false private var isVictory: Boolean = false private var time: Long = 0L private var rightMines: Int = 0 private var totalMines: Int = 0 - private var saveId: Long = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -44,7 +39,6 @@ class EndGameDialogFragment : AppCompatDialogFragment() { rightMines = getInt(DIALOG_RIGHT_MINES, 0) totalMines = getInt(DIALOG_TOTAL_MINES, 0) hasValidData = (totalMines > 0) - saveId = getLong(DIALOG_SAVE_ID, 0L) } } @@ -100,9 +94,7 @@ class EndGameDialogFragment : AppCompatDialogFragment() { setView(view) setPositiveButton(R.string.new_game) { _, _ -> - GlobalScope.launch { - viewModel.startNewGame() - } + viewModel.startNewGame() } when { @@ -115,33 +107,25 @@ class EndGameDialogFragment : AppCompatDialogFragment() { } isVictory -> { setNeutralButton(R.string.share) { _, _ -> - val setup = viewModel.levelSetup.value - val field = viewModel.field.value - - GlobalScope.launch { - shareViewModel.share(setup, field) - } + viewModel.shareObserver.postValue(Unit) } } else -> { setNeutralButton(R.string.retry) { _, _ -> - GlobalScope.launch { - viewModel.retryGame(saveId.toInt()) - } + viewModel.retryObserver.postValue(Unit) } } } }.create() companion object { - fun newInstance(victory: Boolean, rightMines: Int, totalMines: Int, time: Long, saveId: Long) = + fun newInstance(victory: Boolean, rightMines: Int, totalMines: Int, time: Long) = EndGameDialogFragment().apply { arguments = Bundle().apply { putBoolean(DIALOG_IS_VICTORY, victory) putInt(DIALOG_RIGHT_MINES, rightMines) putInt(DIALOG_TOTAL_MINES, totalMines) putLong(DIALOG_TIME, time) - putLong(DIALOG_SAVE_ID, saveId) } } @@ -149,7 +133,6 @@ class EndGameDialogFragment : AppCompatDialogFragment() { private const val DIALOG_TIME = "dialog_time" private const val DIALOG_RIGHT_MINES = "dialog_right_mines" private const val DIALOG_TOTAL_MINES = "dialog_total_mines" - private const val DIALOG_SAVE_ID = "dialog_save_id" val TAG = EndGameDialogFragment::class.simpleName!! } diff --git a/app/src/test/java/dev/lucasnlm/antimine/di/TestLevelModule.kt b/app/src/test/java/dev/lucasnlm/antimine/di/TestLevelModule.kt index d88d28a4..d4c37b66 100644 --- a/app/src/test/java/dev/lucasnlm/antimine/di/TestLevelModule.kt +++ b/app/src/test/java/dev/lucasnlm/antimine/di/TestLevelModule.kt @@ -13,8 +13,8 @@ import dev.lucasnlm.antimine.common.level.repository.IStatsRepository import dev.lucasnlm.antimine.common.level.repository.MemorySavesRepository import dev.lucasnlm.antimine.common.level.repository.MemoryStatsRepository import dev.lucasnlm.antimine.common.level.utils.Clock -import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor -import dev.lucasnlm.antimine.mocks.DisabledHapticFeedbackInteractor +import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackManager +import dev.lucasnlm.antimine.mocks.DisabledHapticFeedbackManager import dev.lucasnlm.antimine.mocks.FixedDimensionRepository import dev.lucasnlm.antimine.mocks.FixedMinefieldRepository @@ -40,5 +40,5 @@ class TestLevelModule { fun provideMinefieldRepository(): IMinefieldRepository = FixedMinefieldRepository() @Provides - fun provideHapticFeedbackInteractor(): IHapticFeedbackInteractor = DisabledHapticFeedbackInteractor() + fun provideHapticFeedbackInteractor(): IHapticFeedbackManager = DisabledHapticFeedbackManager() } diff --git a/app/src/test/java/dev/lucasnlm/antimine/mocks/DisabledHapticFeedbackInteractor.kt b/app/src/test/java/dev/lucasnlm/antimine/mocks/DisabledHapticFeedbackManager.kt similarity index 53% rename from app/src/test/java/dev/lucasnlm/antimine/mocks/DisabledHapticFeedbackInteractor.kt rename to app/src/test/java/dev/lucasnlm/antimine/mocks/DisabledHapticFeedbackManager.kt index 9e22fcd6..fa2f70cb 100644 --- a/app/src/test/java/dev/lucasnlm/antimine/mocks/DisabledHapticFeedbackInteractor.kt +++ b/app/src/test/java/dev/lucasnlm/antimine/mocks/DisabledHapticFeedbackManager.kt @@ -1,8 +1,8 @@ package dev.lucasnlm.antimine.mocks -import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor +import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackManager -class DisabledHapticFeedbackInteractor : IHapticFeedbackInteractor { +class DisabledHapticFeedbackManager : IHapticFeedbackManager { override fun longPressFeedback() { // Empty } diff --git a/common/build.gradle b/common/build.gradle index b4e2fdee..634640fb 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -9,8 +9,8 @@ android { defaultConfig { // versionCode and versionName must be hardcoded to support F-droid - versionCode 702031 - versionName '7.2.3' + versionCode 702041 + versionName '7.2.4' minSdkVersion 16 targetSdkVersion 30 testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/di/LevelModule.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/di/LevelModule.kt index 62b67e2b..661eb19f 100644 --- a/common/src/main/java/dev/lucasnlm/antimine/common/level/di/LevelModule.kt +++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/di/LevelModule.kt @@ -1,7 +1,6 @@ package dev.lucasnlm.antimine.common.level.di import android.content.Context -import androidx.lifecycle.MutableLiveData import androidx.room.Room import dagger.Module import dagger.Provides @@ -11,7 +10,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext import dev.lucasnlm.antimine.common.level.database.AppDataBase import dev.lucasnlm.antimine.common.level.database.dao.SaveDao import dev.lucasnlm.antimine.common.level.database.dao.StatsDao -import dev.lucasnlm.antimine.common.level.models.Event import dev.lucasnlm.antimine.common.level.repository.DimensionRepository import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository @@ -21,8 +19,8 @@ import dev.lucasnlm.antimine.common.level.repository.MinefieldRepository import dev.lucasnlm.antimine.common.level.repository.SavesRepository import dev.lucasnlm.antimine.common.level.repository.StatsRepository import dev.lucasnlm.antimine.common.level.utils.Clock -import dev.lucasnlm.antimine.common.level.utils.HapticFeedbackInteractor -import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor +import dev.lucasnlm.antimine.common.level.utils.HapticFeedbackManager +import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackManager import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository @Module @@ -47,9 +45,6 @@ open class LevelModule { appDataBase: AppDataBase ): StatsDao = appDataBase.statsDao() - @Provides - open fun provideGameEventObserver(): MutableLiveData = MutableLiveData() - @Provides open fun provideClock(): Clock = Clock() @@ -75,10 +70,8 @@ open class LevelModule { @Provides open fun provideHapticFeedbackInteractor( - @ApplicationContext context: Context, - preferencesRepository: IPreferencesRepository - ): IHapticFeedbackInteractor = - HapticFeedbackInteractor(context, preferencesRepository) + @ApplicationContext context: Context + ): IHapticFeedbackManager = HapticFeedbackManager(context) companion object { private const val DATA_BASE_NAME = "saves-db" diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/models/Event.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/models/Event.kt index aed0b4d6..584aaccc 100644 --- a/common/src/main/java/dev/lucasnlm/antimine/common/level/models/Event.kt +++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/models/Event.kt @@ -3,7 +3,9 @@ package dev.lucasnlm.antimine.common.level.models enum class Event { StartNewGame, ResumeGame, + @Deprecated("Use GameOver") ResumeGameOver, + @Deprecated("Use Victory") ResumeVictory, Pause, Resume, diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/utils/HapticFeedbackInteractor.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/utils/HapticFeedbackInteractor.kt deleted file mode 100644 index 8c6f6b12..00000000 --- a/common/src/main/java/dev/lucasnlm/antimine/common/level/utils/HapticFeedbackInteractor.kt +++ /dev/null @@ -1,54 +0,0 @@ -package dev.lucasnlm.antimine.common.level.utils - -import android.content.Context -import android.content.Context.VIBRATOR_SERVICE -import android.os.Build -import android.os.VibrationEffect -import android.os.Vibrator -import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository - -interface IHapticFeedbackInteractor { - fun longPressFeedback() - fun explosionFeedback() -} - -class HapticFeedbackInteractor( - context: Context, - private val preferencesRepository: IPreferencesRepository -) : IHapticFeedbackInteractor { - private val vibrator: Vibrator = context.getSystemService(VIBRATOR_SERVICE) as Vibrator - - override fun longPressFeedback() { - if (preferencesRepository.useHapticFeedback()) { - vibrateTo(70, 240) - vibrateTo(10, 100) - } - } - - override fun explosionFeedback() { - if (preferencesRepository.useHapticFeedback()) { - vibrateTo(400, -1) - } - } - - private fun vibrateTo(time: Long, amplitude: Int) { - if (Build.VERSION.SDK_INT >= 26) { - vibrator.vibrate( - VibrationEffect.createOneShot(time, amplitude) - ) - } else { - @Suppress("DEPRECATION") - vibrator.vibrate(time) - } - } -} - -class DisabledIHapticFeedbackInteractor : IHapticFeedbackInteractor { - override fun longPressFeedback() { - // Empty - } - - override fun explosionFeedback() { - // Empty - } -} diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/utils/HapticFeedbackManager.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/utils/HapticFeedbackManager.kt new file mode 100644 index 00000000..d3e8f5cf --- /dev/null +++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/utils/HapticFeedbackManager.kt @@ -0,0 +1,39 @@ +package dev.lucasnlm.antimine.common.level.utils + +import android.content.Context +import android.content.Context.VIBRATOR_SERVICE +import android.os.Build +import android.os.VibrationEffect +import android.os.Vibrator + +interface IHapticFeedbackManager { + fun longPressFeedback() + fun explosionFeedback() +} + +class HapticFeedbackManager( + context: Context +) : IHapticFeedbackManager { + + private val vibrator by lazy { context.getSystemService(VIBRATOR_SERVICE) as Vibrator } + + override fun longPressFeedback() { + vibrateTo(70, 240) + vibrateTo(10, 100) + } + + override fun explosionFeedback() { + vibrateTo(400, -1) + } + + private fun vibrateTo(time: Long, amplitude: Int) { + if (Build.VERSION.SDK_INT >= 26) { + vibrator.vibrate( + VibrationEffect.createOneShot(time, amplitude) + ) + } else { + @Suppress("DEPRECATION") + vibrator.vibrate(time) + } + } +} diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/view/CommonLevelFragment.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/view/CommonLevelFragment.kt index 59d90b8b..0f84bc81 100644 --- a/common/src/main/java/dev/lucasnlm/antimine/common/level/view/CommonLevelFragment.kt +++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/view/CommonLevelFragment.kt @@ -1,12 +1,10 @@ package dev.lucasnlm.antimine.common.level.view -import android.content.Context import android.os.Bundle -import android.util.DisplayMetrics +import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.WindowManager import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.recyclerview.widget.RecyclerView @@ -19,6 +17,7 @@ import javax.inject.Inject abstract class CommonLevelFragment : Fragment() { @Inject lateinit var dimensionRepository: IDimensionRepository + @Inject lateinit var preferencesRepository: IPreferencesRepository @@ -40,9 +39,8 @@ abstract class CommonLevelFragment : Fragment() { } protected fun calcHorizontalPadding(boardWidth: Int): Int { - val windowManager = requireContext().getSystemService(Context.WINDOW_SERVICE) as WindowManager - val displayMetrics = DisplayMetrics() - windowManager.defaultDisplay.getMetrics(displayMetrics) + val context = requireContext() + val displayMetrics = context.resources.displayMetrics val width = displayMetrics.widthPixels val recyclerViewWidth = (dimensionRepository.areaSize() * boardWidth) @@ -52,14 +50,25 @@ abstract class CommonLevelFragment : Fragment() { protected fun calcVerticalPadding(boardHeight: Int): Int { val context = requireContext() - val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager - val displayMetrics = DisplayMetrics() - windowManager.defaultDisplay.getMetrics(displayMetrics) + val displayMetrics = context.resources.displayMetrics - val height = recyclerGrid.measuredHeight + val typedValue = TypedValue() + val actionBarHeight = if (context.theme.resolveAttribute(android.R.attr.actionBarSize, typedValue, true)) { + TypedValue.complexToDimensionPixelSize(typedValue.data, resources.displayMetrics) + } else { + 0 + } + val resourceId: Int = resources.getIdentifier("navigation_bar_height", "dimen", "android") + val navigationHeight = if (resourceId > 0) { + resources.getDimensionPixelSize(resourceId) + } else 0 + + val height = displayMetrics.heightPixels val recyclerViewHeight = (dimensionRepository.areaSize() * boardHeight) val separatorsHeight = (2 * dimensionRepository.areaSeparator() * (boardHeight - 1)) - return ((height - recyclerViewHeight - separatorsHeight) / 2).coerceAtLeast(0.0f).toInt() + val calculatedHeight = (height - actionBarHeight - navigationHeight - recyclerViewHeight - separatorsHeight) + + return (calculatedHeight / 2).coerceAtLeast(0.0f).toInt() } } diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt index 841a2e4f..1cbb6e7e 100644 --- a/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt +++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt @@ -4,6 +4,7 @@ import android.os.Handler import androidx.hilt.lifecycle.ViewModelInject import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import dev.lucasnlm.antimine.common.R import dev.lucasnlm.antimine.common.level.GameController import dev.lucasnlm.antimine.common.level.database.models.FirstOpen import dev.lucasnlm.antimine.common.level.models.Area @@ -16,13 +17,14 @@ import dev.lucasnlm.antimine.common.level.repository.IMinefieldRepository import dev.lucasnlm.antimine.common.level.repository.ISavesRepository import dev.lucasnlm.antimine.common.level.repository.IStatsRepository import dev.lucasnlm.antimine.common.level.utils.Clock -import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor +import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackManager import dev.lucasnlm.antimine.core.analytics.AnalyticsManager import dev.lucasnlm.antimine.core.analytics.models.Analytics import dev.lucasnlm.antimine.core.control.ActionResponse import dev.lucasnlm.antimine.core.control.ActionFeedback import dev.lucasnlm.antimine.core.control.GameControl import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository +import dev.lucasnlm.antimine.core.sound.ISoundManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay @@ -30,16 +32,20 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class GameViewModel @ViewModelInject constructor( - val eventObserver: MutableLiveData, private val savesRepository: ISavesRepository, private val statsRepository: IStatsRepository, private val dimensionRepository: IDimensionRepository, private val preferencesRepository: IPreferencesRepository, - private val hapticFeedbackInteractor: IHapticFeedbackInteractor, + private val hapticFeedbackManager: IHapticFeedbackManager, + private val soundManager: ISoundManager, private val minefieldRepository: IMinefieldRepository, private val analyticsManager: AnalyticsManager, private val clock: Clock ) : ViewModel() { + val eventObserver = MutableLiveData() + val retryObserver = MutableLiveData() + val shareObserver = MutableLiveData() + private lateinit var gameController: GameController private var currentDifficulty: Difficulty = Difficulty.Standard private var initialized = false @@ -118,8 +124,8 @@ class GameViewModel @ViewModelInject constructor( plantMinesExcept(save.firstOpen.value, true) singleClick(save.firstOpen.value) } - refreshUserPreferences() } + refreshUserPreferences() mineCount.postValue(setup.mines) difficulty.postValue(save.difficulty) @@ -234,7 +240,9 @@ class GameViewModel @ViewModelInject constructor( onFeedbackAnalytics(feedback) onPostAction() - hapticFeedbackInteractor.longPressFeedback() + if (preferencesRepository.useHapticFeedback()) { + hapticFeedbackManager.longPressFeedback() + } } fun onSingleClick(index: Int) { @@ -278,7 +286,6 @@ class GameViewModel @ViewModelInject constructor( private fun updateGameState() { when { gameController.hasAnyMineExploded() -> { - hapticFeedbackInteractor.explosionFeedback() eventObserver.postValue(Event.GameOver) } else -> { @@ -322,12 +329,22 @@ class GameViewModel @ViewModelInject constructor( fun explosionDelay() = if (preferencesRepository.useAnimations()) 750L else 0L - suspend fun gameOver() { + suspend fun gameOver(fromResumeGame: Boolean) { gameController.run { analyticsManager.sentEvent(Analytics.GameOver(clock.time(), getScore())) val explosionTime = (explosionDelay() / gameController.getMinesCount().coerceAtLeast(10)) val delayMillis = explosionTime.coerceAtLeast(25L) + if (!fromResumeGame) { + if (preferencesRepository.useHapticFeedback()) { + hapticFeedbackManager.explosionFeedback() + } + + if (preferencesRepository.isSoundEffectsEnabled()) { + soundManager.play(R.raw.mine_explosion_sound) + } + } + findExplodedMine()?.let { exploded -> takeExplosionRadius(exploded).forEach { it.isCovered = false diff --git a/common/src/main/java/dev/lucasnlm/antimine/core/di/CommonModule.kt b/common/src/main/java/dev/lucasnlm/antimine/core/di/CommonModule.kt index d2f79069..4ee3973a 100644 --- a/common/src/main/java/dev/lucasnlm/antimine/core/di/CommonModule.kt +++ b/common/src/main/java/dev/lucasnlm/antimine/core/di/CommonModule.kt @@ -11,24 +11,27 @@ import dev.lucasnlm.antimine.core.analytics.DebugAnalyticsManager import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository import dev.lucasnlm.antimine.core.preferences.PreferencesManager import dev.lucasnlm.antimine.core.preferences.PreferencesRepository -import javax.inject.Singleton +import dev.lucasnlm.antimine.core.sound.ISoundManager +import dev.lucasnlm.antimine.core.sound.SoundManager @Module @InstallIn(ApplicationComponent::class) class CommonModule { - @Singleton @Provides fun providePreferencesRepository( preferencesManager: PreferencesManager ): IPreferencesRepository = PreferencesRepository(preferencesManager) - @Singleton @Provides fun providePreferencesInteractor( @ApplicationContext context: Context ): PreferencesManager = PreferencesManager(context) - @Singleton @Provides fun provideAnalyticsManager(): AnalyticsManager = DebugAnalyticsManager() + + @Provides + fun provideSoundManager( + @ApplicationContext context: Context + ): ISoundManager = SoundManager(context) } diff --git a/common/src/main/java/dev/lucasnlm/antimine/core/preferences/PreferencesRepository.kt b/common/src/main/java/dev/lucasnlm/antimine/core/preferences/PreferencesRepository.kt index c4b74664..9ea18fff 100644 --- a/common/src/main/java/dev/lucasnlm/antimine/core/preferences/PreferencesRepository.kt +++ b/common/src/main/java/dev/lucasnlm/antimine/core/preferences/PreferencesRepository.kt @@ -20,6 +20,7 @@ interface IPreferencesRepository { fun useLargeAreas(): Boolean fun useAnimations(): Boolean fun useQuestionMark(): Boolean + fun isSoundEffectsEnabled(): Boolean } class PreferencesRepository( @@ -68,7 +69,10 @@ class PreferencesRepository( getBoolean(PREFERENCE_ANIMATION, true) override fun useQuestionMark(): Boolean = - getBoolean(PREFERENCE_QUESTION_MARK, true) + getBoolean(PREFERENCE_QUESTION_MARK, false) + + override fun isSoundEffectsEnabled(): Boolean = + getBoolean(PREFERENCE_SOUND_EFFECTS, false) override fun controlStyle(): ControlStyle { val index = getInt(PREFERENCE_CONTROL_STYLE, -1) @@ -100,5 +104,6 @@ class PreferencesRepository( private const val PREFERENCE_CUSTOM_GAME_WIDTH = "preference_custom_game_width" private const val PREFERENCE_CUSTOM_GAME_HEIGHT = "preference_custom_game_height" private const val PREFERENCE_CUSTOM_GAME_MINES = "preference_custom_game_mines" + private const val PREFERENCE_SOUND_EFFECTS = "preference_sound" } } diff --git a/common/src/main/java/dev/lucasnlm/antimine/core/sound/SoundManager.kt b/common/src/main/java/dev/lucasnlm/antimine/core/sound/SoundManager.kt new file mode 100644 index 00000000..56d625d0 --- /dev/null +++ b/common/src/main/java/dev/lucasnlm/antimine/core/sound/SoundManager.kt @@ -0,0 +1,23 @@ +package dev.lucasnlm.antimine.core.sound + +import android.content.Context +import android.media.MediaPlayer +import androidx.annotation.RawRes + +interface ISoundManager { + fun play(@RawRes soundId: Int) +} + +class SoundManager( + private val context: Context +) : ISoundManager { + + override fun play(@RawRes soundId: Int) { + MediaPlayer.create(context, soundId).apply { + setVolume(0.7f, 0.7f) + setOnCompletionListener { + release() + } + }.start() + } +} diff --git a/common/src/main/res/values-ca-rES/strings.xml b/common/src/main/res/values-ca-rES/strings.xml index 27ef4cc7..f5789097 100644 --- a/common/src/main/res/values-ca-rES/strings.xml +++ b/common/src/main/res/values-ca-rES/strings.xml @@ -1,71 +1,71 @@ - Antimine - You have to clear a rectangular board containing hidden mines without detonating any of them. - Games - Previous Games - Difficulty - Standard - Beginner - Intermediate + Antimines + Has de netejar un tauler rectangular amb mines amagades sense detonar-ne cap. + Jocs + Partides prèvies + Camp de mines + Estàndard + Principiant + Intermedi Expert - Open - Settings - Animations - Haptic Feedback - About - Statistics - Custom - Start - Width - Height + Obert + Configuració + Animacions + Resposta hàptica + Quant a + Estadístiques + Personalitzat + Inicia + Amplada + Alçada Mines - If you start a new game, your current progress will be lost. - Show Licenses - Do you want to start a new game? + Si comences un altre joc, el teu progrés actual es perdrà. + Mostra les llicències + Vols començar un joc nou? %d mines - Game Time - Mine + Temps de joc + Mina General - Gameplay - Accessibility - Use Large Areas - Feedback - If you like this game, please give us a feedback. It will help us a lot. - This game uses the following third parties software: - This game was translated by the following people: - Failed to sign in. Please check your network connection and try again. + Joc + Accessibilitat + Usa grans àrees + Opina + Si t\'agrada aquest joc, si us plau dona\'ns la teva opinió. Ens ajudarà molt. + Aquest joc usa el següent programari de tercers: + Aquest joc ha estat traduït per aquestes persones: + No s\'ha pogut connectar. Si us plau verifica la teva connexió de xarxa i intenta-ho de nou. You\'ll lose all moves on current game.\nBut you can also install the game before quit. - You won! - Victories - You lost! - Defeats - Good luck on your next game. + Has guanyat! + Victòries + Has perdut! + Derrotes + Que tinguis sort en el proper joc. You finished the minefield in %1$d seconds. - Failed to share - Version %1$s - Sound Effects - Quit - Are you sure? + No s\'ha pogut compartir + Versió %1$s + Efecte de so + Surt + Esteu segurs? Enable automatic placing of flags Open Areas - Total Time + Temps total Average Time - Performance + Rendiment OK Use Question Mark Controls Flag First Single Click - Double Click - Long Press + Doble clic + Pulsació llarga Open Tile Flag Tile - Retry + Reintenta Empty - Unknown error. - Leaderboards + Error desconegut. + Llista de líders Cancel Resume Yes @@ -91,9 +91,9 @@ Marked area Doubtful area Wrongly marked area - Exploded Mine - Game Started - You exploded a mine! - Flag placed! - Flag removed! + Mina explotada + La partida ha començat + Has explotat una mina! + Bandera col·locada! + Has tret la bandera! diff --git a/common/src/main/res/values-fr-rFR/strings.xml b/common/src/main/res/values-fr-rFR/strings.xml index 78376872..57608bc3 100644 --- a/common/src/main/res/values-fr-rFR/strings.xml +++ b/common/src/main/res/values-fr-rFR/strings.xml @@ -42,12 +42,12 @@ Défaites Bonne chance pour votre prochaine partie. Vous avez déminé le champ de mine en %1$d secondes. - Le partage a échoué + Échec du partage Version %1$s Effets sonores Quitter Vraiment ? - Activer le placement automatique des drapeaux + Activer le placement automatique de drapeaux Zones ouvertes Temps de jeu total Temps de jeu moyen @@ -57,9 +57,9 @@ Contrôles Drapeau rapide - Simple clic + Clic simple Double clic - Appui Long + Appui prolongé Ouvrir la tuile Placer le drapeau Réessayer diff --git a/common/src/main/res/values-in-rID/strings.xml b/common/src/main/res/values-in-rID/strings.xml new file mode 100644 index 00000000..1c7e64ea --- /dev/null +++ b/common/src/main/res/values-in-rID/strings.xml @@ -0,0 +1,99 @@ + + + Antimine + Kosongkan ranjau tersembunyi dari medan ranjau + Permainan + Tantangan Sebelumnya + Kesulitan + Standar + Pemula + Menengah + Ahli + Buka + Pengaturan + Animasi + Umpan Balik Haptik + Tentang + Statistik + Kustom + Mulai + Lebar + Height + Tambang + Bila Anda memulai permainan baru, kemajuan Anda kini akan hilang. + Tampilkan lisensi + Apakah Anda ingin memulai permainan baru? + %d ranjau + Waktu permainan + Ranjau + Umum + Aspek permainan + Aksesibilitas + Use Large Areas + Umpan Balik + Jika Anda menyukai game ini, tolong beri kami umpan balik. Itu akan banyak membantu kita. + This game uses the following third parties software: + This game was translated by the following people: + Tidak dapat tersambung. Silakan periksa koneksi jaringan anda dan coba lagi. + Kemajuan Anda kini akan hilang. + Anda menang! + Kemenangan + Anda Kalah! + Kalah + Semoga sukses di game berikutnya. + Anda menyelesaikan permainan dalam %1$d detik. + Gagal berbagi + Versi %1$s + Efek suara + Keluar + Anda yakin? + Aktifkan penempatan bendera otomatis + Buka + Total waktu + Waktu rata-rata + Kinerja + OK + Pakai Bendera Tanda Tanya + Kontrol + + Bendera + Satu klik + Klik dua kali + Tekan Lama + Buka + Bendera + Ulangi + Kosong + Galat tak diketahui. + Papan Peringkat + Batal + Lanjutkan + Ya + Tidak + Umum + Kode sumber + Alih Bahasa + Licenses + Google Play Games + Pasang + Menyambungkan + Menghubungkan… + Terputus + Memutuskan + Permainan Baru + Bagikan + Bagikan… + Tidak ada sambungan Internet. + Buka menu + Tutup Menu + All mines were disabled. + Covered area + Marked area + Doubtful area + Wrongly marked area + Exploded Mine + Game Started + You exploded a mine! + Flag placed! + Flag removed! + diff --git a/common/src/main/res/values-zh-rCN/strings.xml b/common/src/main/res/values-zh-rCN/strings.xml index 283e112a..814ae7ad 100644 --- a/common/src/main/res/values-zh-rCN/strings.xml +++ b/common/src/main/res/values-zh-rCN/strings.xml @@ -52,16 +52,16 @@ 总用时 平均用时 性能 - 好的 - 使用问号旗标 + 确定 + 使用问号标记 控制 标记在前 - 单次点击 + 单击 双击 长按 - 打开 - + 打开图块 + 旗帜图块 重试 未知错误。 diff --git a/common/src/main/res/xml/preferences.xml b/common/src/main/res/xml/preferences.xml index 4a5488b7..eadc5e28 100644 --- a/common/src/main/res/xml/preferences.xml +++ b/common/src/main/res/xml/preferences.xml @@ -8,8 +8,8 @@ app:iconSpaceReserved="false"> @@ -35,8 +35,8 @@ app:iconSpaceReserved="false"/> diff --git a/common/src/test/java/dev/lucasnlm/antimine/common/level/di/DisabledHapticFeedbackManager.kt b/common/src/test/java/dev/lucasnlm/antimine/common/level/di/DisabledHapticFeedbackManager.kt new file mode 100644 index 00000000..90514525 --- /dev/null +++ b/common/src/test/java/dev/lucasnlm/antimine/common/level/di/DisabledHapticFeedbackManager.kt @@ -0,0 +1,13 @@ +package dev.lucasnlm.antimine.common.level.di + +import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackManager + +class DisabledHapticFeedbackManager : IHapticFeedbackManager { + override fun longPressFeedback() { + // Empty + } + + override fun explosionFeedback() { + // Empty + } +} diff --git a/common/src/test/java/dev/lucasnlm/antimine/common/level/di/TestLevelModule.kt b/common/src/test/java/dev/lucasnlm/antimine/common/level/di/TestLevelModule.kt index eba558dd..57b49020 100644 --- a/common/src/test/java/dev/lucasnlm/antimine/common/level/di/TestLevelModule.kt +++ b/common/src/test/java/dev/lucasnlm/antimine/common/level/di/TestLevelModule.kt @@ -14,8 +14,7 @@ import dev.lucasnlm.antimine.common.level.repository.IStatsRepository import dev.lucasnlm.antimine.common.level.repository.MemorySavesRepository import dev.lucasnlm.antimine.common.level.repository.MemoryStatsRepository import dev.lucasnlm.antimine.common.level.utils.Clock -import dev.lucasnlm.antimine.common.level.utils.DisabledIHapticFeedbackInteractor -import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor +import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackManager @Module @InstallIn(ApplicationComponent::class) @@ -39,5 +38,5 @@ class TestLevelModule { fun provideStatsRepository(): IStatsRepository = MemoryStatsRepository() @Provides - fun provideHapticFeedbackInteractor(): IHapticFeedbackInteractor = DisabledIHapticFeedbackInteractor() + fun provideHapticFeedbackInteractor(): IHapticFeedbackManager = DisabledHapticFeedbackManager() } diff --git a/foss/build.gradle b/foss/build.gradle index 105d4318..a9585adb 100644 --- a/foss/build.gradle +++ b/foss/build.gradle @@ -6,8 +6,8 @@ android { compileSdkVersion 30 defaultConfig { - versionCode 702031 // MMmmPPv - versionName '7.2.3' + versionCode 702041 // MMmmPPv + versionName '7.2.4' minSdkVersion 16 targetSdkVersion 30 } diff --git a/proprietary/build.gradle b/proprietary/build.gradle index 9bb382da..fe342f7d 100644 --- a/proprietary/build.gradle +++ b/proprietary/build.gradle @@ -6,8 +6,8 @@ android { compileSdkVersion 30 defaultConfig { - versionCode 702031 // MMmmPPv - versionName '7.2.3' + versionCode 702041 // MMmmPPv + versionName '7.2.4' minSdkVersion 16 targetSdkVersion 30 } diff --git a/wear/build.gradle b/wear/build.gradle index 9db6ad46..626e9f4b 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -9,8 +9,8 @@ android { defaultConfig { // versionCode and versionName must be hardcoded to support F-droid - versionCode 702031 - versionName '7.2.3' + versionCode 702041 + versionName '7.2.4' applicationId 'dev.lucasnlm.antimine' minSdkVersion 23 targetSdkVersion 30 diff --git a/wear/src/main/java/dev/lucasnlm/antimine/wear/WatchGameActivity.kt b/wear/src/main/java/dev/lucasnlm/antimine/wear/WatchGameActivity.kt index 536e6d67..c41a2c9e 100644 --- a/wear/src/main/java/dev/lucasnlm/antimine/wear/WatchGameActivity.kt +++ b/wear/src/main/java/dev/lucasnlm/antimine/wear/WatchGameActivity.kt @@ -176,7 +176,7 @@ class WatchGameActivity : AppCompatActivity(), AmbientModeSupport.AmbientCallbac viewModel.stopClock() GlobalScope.launch(context = Dispatchers.Main) { - viewModel.gameOver() + viewModel.gameOver(false) messageText.text = getString(R.string.game_over) waitAndShowNewGameButton() }