Merge pull request #220 from lucasnlm/add-recycler-padding
Add recycler padding
This commit is contained in:
commit
0d6c6cfd4a
28 changed files with 198 additions and 93 deletions
|
@ -15,8 +15,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
// versionCode and versionName must be hardcoded to support F-droid
|
||||
versionCode 805011
|
||||
versionName '8.5.1'
|
||||
versionCode 805021
|
||||
versionName '8.5.2'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
multiDexEnabled true
|
||||
|
|
|
@ -10,6 +10,7 @@ import android.view.Gravity
|
|||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
|
@ -68,6 +69,7 @@ import kotlinx.coroutines.delay
|
|||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.singleOrNull
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import java.lang.Exception
|
||||
|
@ -121,6 +123,7 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
bindNavigationMenu()
|
||||
bindSwitchControlButton()
|
||||
bindAds()
|
||||
bindPrice()
|
||||
|
||||
findViewById<FrameLayout>(R.id.levelContainer).doOnLayout {
|
||||
if (!isFinishing) {
|
||||
|
@ -488,14 +491,11 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
findItem(R.id.share_now).isVisible = isNotInstant
|
||||
findItem(R.id.remove_ads).isVisible = !preferencesRepository.isPremiumEnabled() && isNotInstant
|
||||
findItem(R.id.previous_games).isVisible = featureFlagManager.isGameHistoryEnabled()
|
||||
findItem(R.id.rate).isVisible = featureFlagManager.isRateUsEnabled()
|
||||
|
||||
if (!playGamesManager.hasGooglePlayGames()) {
|
||||
removeGroup(R.id.play_games_group)
|
||||
}
|
||||
|
||||
// New Features
|
||||
findItem(R.id.themes).setActionView(R.layout.new_icon)
|
||||
findItem(R.id.tutorial).setActionView(R.layout.new_icon)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -899,6 +899,36 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
}
|
||||
}
|
||||
|
||||
private fun bindPrice() {
|
||||
if (billingManager.isEnabled() && !preferencesRepository.isPremiumEnabled()) {
|
||||
billingManager.start()
|
||||
|
||||
lifecycleScope.launchWhenResumed {
|
||||
billingManager.getPrice().collect { price ->
|
||||
if (price.isNotBlank()) {
|
||||
try {
|
||||
navigationView.menu.findItem(R.id.remove_ads).apply {
|
||||
actionView = TextView(baseContext).apply {
|
||||
text = price
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
setTextColor(ContextCompat.getColor(context, R.color.mines_around_2))
|
||||
layoutParams = FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
).apply {
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Fail to create price text")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshAds() {
|
||||
val isTutorialComplete = preferencesRepository.isTutorialCompleted()
|
||||
if (isTutorialComplete && !preferencesRepository.isPremiumEnabled() && billingManager.isEnabled()) {
|
||||
|
@ -946,12 +976,16 @@ class GameActivity : ThematicActivity(R.layout.activity_game), DialogInterface.O
|
|||
if (supportFragmentManager.findFragmentByTag(SupportAppDialogFragment.TAG) == null &&
|
||||
!instantAppManager.isEnabled(this)
|
||||
) {
|
||||
if (billingManager.isEnabled()) {
|
||||
SupportAppDialogFragment.newRemoveAdsSupportDialog()
|
||||
.show(supportFragmentManager, SupportAppDialogFragment.TAG)
|
||||
} else {
|
||||
SupportAppDialogFragment.newRequestSupportDialog()
|
||||
.show(supportFragmentManager, SupportAppDialogFragment.TAG)
|
||||
lifecycleScope.launch {
|
||||
if (billingManager.isEnabled()) {
|
||||
SupportAppDialogFragment.newRemoveAdsSupportDialog(
|
||||
applicationContext,
|
||||
billingManager.getPrice().singleOrNull()
|
||||
).show(supportFragmentManager, SupportAppDialogFragment.TAG)
|
||||
} else {
|
||||
SupportAppDialogFragment.newRequestSupportDialog(applicationContext)
|
||||
.show(supportFragmentManager, SupportAppDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ class AboutActivity : ThematicActivity(R.layout.activity_empty) {
|
|||
|
||||
private fun showSupportAppDialog() {
|
||||
if (supportFragmentManager.findFragmentByTag(SupportAppDialogFragment.TAG) == null) {
|
||||
SupportAppDialogFragment.newRequestSupportDialog()
|
||||
SupportAppDialogFragment.newRequestSupportDialog(applicationContext)
|
||||
.show(supportFragmentManager, SupportAppDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import dev.lucasnlm.antimine.R
|
|||
import dev.lucasnlm.antimine.about.viewmodel.AboutEvent
|
||||
import dev.lucasnlm.antimine.about.viewmodel.AboutViewModel
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import dev.lucasnlm.external.IBillingManager
|
||||
import kotlinx.android.synthetic.main.fragment_about_info.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
|
@ -17,6 +18,7 @@ import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
|||
class AboutInfoFragment : Fragment(R.layout.fragment_about_info) {
|
||||
private val aboutViewModel: AboutViewModel by sharedViewModel()
|
||||
private val preferencesRepository: IPreferencesRepository by inject()
|
||||
private val billingManager: IBillingManager by inject()
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
@ -38,12 +40,12 @@ class AboutInfoFragment : Fragment(R.layout.fragment_about_info) {
|
|||
}
|
||||
}
|
||||
|
||||
if (preferencesRepository.showSupport()) {
|
||||
if (preferencesRepository.isPremiumEnabled() && billingManager.isEnabled()) {
|
||||
supportUs.visibility = View.GONE
|
||||
} else {
|
||||
supportUs.setOnClickListener {
|
||||
aboutViewModel.sendEvent(AboutEvent.SupportUs)
|
||||
}
|
||||
} else {
|
||||
supportUs.visibility = View.GONE
|
||||
}
|
||||
|
||||
thirdsParties.setOnClickListener {
|
||||
|
|
|
@ -67,6 +67,19 @@ open class LevelFragment : CommonLevelFragment(R.layout.fragment_level) {
|
|||
eventObserver.observe(
|
||||
viewLifecycleOwner,
|
||||
{
|
||||
if (!gameViewModel.hasPlantedMines()) {
|
||||
recyclerGrid.post {
|
||||
levelSetup.value?.let { minefield ->
|
||||
val size = dimensionRepository.areaSizeWithPadding()
|
||||
val actionBarSize = dimensionRepository.actionBarSize()
|
||||
val dx = minefield.width * size * 0.25f
|
||||
val dy = (minefield.height * size + actionBarSize) * 0.25f - size * 3
|
||||
|
||||
recyclerGrid.smoothScrollBy(dx.toInt(), dy.toInt(), null, 200)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (it) {
|
||||
Event.Pause,
|
||||
Event.GameOver,
|
||||
|
|
|
@ -2,6 +2,7 @@ package dev.lucasnlm.antimine.splash.viewmodel
|
|||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dev.lucasnlm.antimine.common.level.database.models.Stats
|
||||
import dev.lucasnlm.antimine.common.level.repository.IStatsRepository
|
||||
import dev.lucasnlm.antimine.core.control.ControlStyle
|
||||
|
@ -9,6 +10,7 @@ import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
|||
import dev.lucasnlm.antimine.support.IapHandler
|
||||
import dev.lucasnlm.external.ICloudStorageManager
|
||||
import dev.lucasnlm.external.model.CloudSave
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SplashViewModel(
|
||||
private val preferencesRepository: IPreferencesRepository,
|
||||
|
@ -17,7 +19,9 @@ class SplashViewModel(
|
|||
private val iapHandler: IapHandler,
|
||||
) : ViewModel() {
|
||||
fun startIap() {
|
||||
iapHandler.start()
|
||||
viewModelScope.launch {
|
||||
iapHandler.start()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun migrateCloudSave(playGamesId: String) {
|
||||
|
|
|
@ -7,9 +7,7 @@ import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
|||
import dev.lucasnlm.external.IBillingManager
|
||||
import dev.lucasnlm.external.model.PurchaseInfo
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class IapHandler(
|
||||
|
@ -17,14 +15,12 @@ class IapHandler(
|
|||
private val preferencesManager: IPreferencesRepository,
|
||||
private val billingManager: IBillingManager,
|
||||
) {
|
||||
fun start() {
|
||||
GlobalScope.launch {
|
||||
billingManager.listenPurchases().collect {
|
||||
if (it is PurchaseInfo.PurchaseResult) {
|
||||
onLockStatusChanged(it.unlockStatus)
|
||||
} else {
|
||||
showFailToConnectFeedback()
|
||||
}
|
||||
suspend fun start() {
|
||||
billingManager.listenPurchases().collect {
|
||||
if (it is PurchaseInfo.PurchaseResult) {
|
||||
onLockStatusChanged(it.unlockStatus)
|
||||
} else {
|
||||
showFailToConnectFeedback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package dev.lucasnlm.antimine.support
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
|
@ -27,7 +29,7 @@ class SupportAppDialogFragment : AppCompatDialogFragment() {
|
|||
private val adsManager: IAdsManager by inject()
|
||||
private val instantAppManager: InstantAppManager by inject()
|
||||
|
||||
private var unlockMessage: Int = R.string.support_action
|
||||
private lateinit var unlockMessage: String
|
||||
private var targetThemeId: Long = -1L
|
||||
|
||||
private var isInstantMode: Boolean = true
|
||||
|
@ -40,11 +42,13 @@ class SupportAppDialogFragment : AppCompatDialogFragment() {
|
|||
analyticsManager.sentEvent(Analytics.ShowIapDialog)
|
||||
|
||||
unlockMessage =
|
||||
(arguments?.getInt(UNLOCK_LABEL) ?: savedInstanceState?.getInt(UNLOCK_LABEL)) ?: R.string.support_action
|
||||
(arguments?.getString(UNLOCK_LABEL) ?: savedInstanceState?.getString(UNLOCK_LABEL))
|
||||
?: getString(R.string.support_action)
|
||||
targetThemeId =
|
||||
(arguments?.getLong(TARGET_THEME_ID, -1L)) ?: -1L
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return AlertDialog.Builder(requireContext()).apply {
|
||||
val view = LayoutInflater
|
||||
|
@ -148,26 +152,30 @@ class SupportAppDialogFragment : AppCompatDialogFragment() {
|
|||
private const val UNLOCK_LABEL = "support_unlock_label"
|
||||
private const val TARGET_THEME_ID = "target_theme_id"
|
||||
|
||||
fun newRequestSupportDialog(): SupportAppDialogFragment {
|
||||
fun newRequestSupportDialog(context: Context): SupportAppDialogFragment {
|
||||
return SupportAppDialogFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putInt(UNLOCK_LABEL, R.string.support_action)
|
||||
putString(UNLOCK_LABEL, context.getString(R.string.support_action))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun newRemoveAdsSupportDialog(): SupportAppDialogFragment {
|
||||
fun newRemoveAdsSupportDialog(context: Context, price: String?): SupportAppDialogFragment {
|
||||
return SupportAppDialogFragment().apply {
|
||||
val label = context.getString(R.string.remove_ad)
|
||||
val unlockLabel = price?.let { "$label - $it" } ?: label
|
||||
arguments = Bundle().apply {
|
||||
putInt(UNLOCK_LABEL, R.string.remove_ad)
|
||||
putString(UNLOCK_LABEL, unlockLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun newChangeThemeDialog(targetThemeId: Long): SupportAppDialogFragment {
|
||||
fun newChangeThemeDialog(context: Context, targetThemeId: Long, price: String?): SupportAppDialogFragment {
|
||||
return SupportAppDialogFragment().apply {
|
||||
val label = context.getString(R.string.unlock_all)
|
||||
val unlockLabel = price?.let { "$label - $it" } ?: label
|
||||
arguments = Bundle().apply {
|
||||
putInt(UNLOCK_LABEL, R.string.unlock_all)
|
||||
putString(UNLOCK_LABEL, unlockLabel)
|
||||
putLong(TARGET_THEME_ID, targetThemeId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,11 @@ import dev.lucasnlm.antimine.support.SupportAppDialogFragment
|
|||
import dev.lucasnlm.antimine.theme.view.ThemeAdapter
|
||||
import dev.lucasnlm.antimine.theme.viewmodel.ThemeEvent
|
||||
import dev.lucasnlm.antimine.theme.viewmodel.ThemeViewModel
|
||||
import dev.lucasnlm.external.IBillingManager
|
||||
import kotlinx.android.synthetic.main.activity_game.*
|
||||
import kotlinx.android.synthetic.main.activity_theme.*
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.singleOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
@ -30,6 +33,8 @@ class ThemeActivity : ThematicActivity(R.layout.activity_theme) {
|
|||
|
||||
private val preferencesRepository: IPreferencesRepository by inject()
|
||||
|
||||
private val billingManager: IBillingManager by inject()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
lifecycleScope.launchWhenCreated {
|
||||
|
@ -90,8 +95,14 @@ class ThemeActivity : ThematicActivity(R.layout.activity_theme) {
|
|||
|
||||
private fun showUnlockDialog(themeId: Long) {
|
||||
if (supportFragmentManager.findFragmentByTag(SupportAppDialogFragment.TAG) == null) {
|
||||
SupportAppDialogFragment.newChangeThemeDialog(themeId).apply {
|
||||
show(supportFragmentManager, SupportAppDialogFragment.TAG)
|
||||
lifecycleScope.launch {
|
||||
SupportAppDialogFragment.newChangeThemeDialog(
|
||||
applicationContext,
|
||||
themeId,
|
||||
billingManager.getPrice().singleOrNull()
|
||||
).apply {
|
||||
show(supportFragmentManager, SupportAppDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ val AppModule = module {
|
|||
|
||||
override suspend fun charge(activity: Activity) { }
|
||||
|
||||
override fun getPrice(): Flow<String> = flowOf()
|
||||
|
||||
override fun listenPurchases(): Flow<PurchaseInfo> = flowOf()
|
||||
}
|
||||
} bind IBillingManager::class
|
||||
|
|
|
@ -10,8 +10,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
// versionCode and versionName must be hardcoded to support F-droid
|
||||
versionCode 805011
|
||||
versionName '8.5.1'
|
||||
versionCode 805021
|
||||
versionName '8.5.2'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
|
|
@ -17,8 +17,8 @@ import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
|||
import kotlin.math.nextDown
|
||||
|
||||
abstract class CommonLevelFragment(@LayoutRes val contentLayoutId: Int) : Fragment(contentLayoutId) {
|
||||
private val dimensionRepository: IDimensionRepository by inject()
|
||||
private val preferencesRepository: IPreferencesRepository by inject()
|
||||
protected val dimensionRepository: IDimensionRepository by inject()
|
||||
protected val gameViewModel by sharedViewModel<GameViewModel>()
|
||||
protected val areaAdapter by lazy {
|
||||
AreaAdapter(requireContext(), gameViewModel, preferencesRepository, dimensionRepository)
|
||||
|
@ -39,7 +39,12 @@ abstract class CommonLevelFragment(@LayoutRes val contentLayoutId: Int) : Fragme
|
|||
recyclerGrid.apply {
|
||||
val horizontalPadding = calcHorizontalPadding(view, levelSetup.width)
|
||||
val verticalPadding = calcVerticalPadding(view, levelSetup.height)
|
||||
setPadding(horizontalPadding, verticalPadding, 0, 0)
|
||||
if (horizontalPadding == 0 && verticalPadding == 0) {
|
||||
val minPadding = dimensionRepository.areaSize().toInt()
|
||||
setPadding(minPadding, minPadding, minPadding, minPadding)
|
||||
} else {
|
||||
setPadding(horizontalPadding, verticalPadding, 0, 0)
|
||||
}
|
||||
layoutManager = makeNewLayoutManager(levelSetup.width)
|
||||
adapter = areaAdapter
|
||||
}
|
||||
|
|
|
@ -384,6 +384,10 @@ open class GameViewModel(
|
|||
gameController.revealAllEmptyAreas()
|
||||
}
|
||||
|
||||
fun hasPlantedMines(): Boolean {
|
||||
return gameController.mines().isNotEmpty()
|
||||
}
|
||||
|
||||
fun revealRandomMine(): Boolean {
|
||||
return if (gameController.revealRandomMine()) {
|
||||
if (tipRepository.removeTip()) {
|
||||
|
|
|
@ -565,7 +565,7 @@ class FixedGridLayoutManager : RecyclerView.LayoutManager() {
|
|||
// Check right bound
|
||||
if (rightBoundReached) {
|
||||
// If we've reached the last column, enforce limits
|
||||
val rightOffset = horizontalSpace - getDecoratedRight(bottomView) + paddingRight
|
||||
val rightOffset = horizontalSpace - getDecoratedRight(bottomView) - paddingRight
|
||||
(-dx).coerceAtLeast(rightOffset)
|
||||
} else {
|
||||
// No limits while the last column isn't visible
|
||||
|
@ -648,7 +648,7 @@ class FixedGridLayoutManager : RecyclerView.LayoutManager() {
|
|||
val bottomOffset: Int =
|
||||
if (rowOfIndex(childCount - 1) >= maxRowCount - 1) {
|
||||
// We are truly at the bottom, determine how far
|
||||
verticalSpace - getDecoratedBottom(bottomView) +
|
||||
verticalSpace - getDecoratedBottom(bottomView) -
|
||||
paddingBottom
|
||||
} else {
|
||||
/*
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:locale="fr">
|
||||
<string name="tutorial">Tutoriel</string>
|
||||
<string name="tutorial_completed">Tutoriel Terminé</string>
|
||||
<string name="tutorial">Didacticiel</string>
|
||||
<string name="tutorial_completed">Didacticiel terminé</string>
|
||||
<string name="tutorial_0_bottom">Vous pouvez commencer par %1$s à n\'importe quel carré aléatoire.</string>
|
||||
<string name="tutorial_1_top">Les chiffres indiquent combien de mines sont adjacentes au carré surligné.</string>
|
||||
<string name="tutorial_1_bottom">Par conséquent, si le coin 1️⃣ n\'a qu\'un carré adjacent, il doit s\'agir d\'une mine. %1$s pour le signaler.</string>
|
||||
|
@ -9,7 +9,7 @@
|
|||
<string name="tutorial_2_bottom">Vous pouvez l\'ouvrir par %1$s.</string>
|
||||
<string name="tutorial_3_top">Il en va de même pour 1️⃣ . Vous savez où se trouve la mine adjacente.</string>
|
||||
<string name="tutorial_3_bottom">Vous pouvez ouvrir tous les autres carrés non ouverts adjacents à 1️⃣ .</string>
|
||||
<string name="tutorial_4_top">Nous savons où se trouve l\'une des mines, donc il n\'y a qu\'une seule possibilité pour l\'autre.</string>
|
||||
<string name="tutorial_4_top">Nous savons où se trouve l\'une des mines. Il n\'y a donc qu\'une seule possibilité pour l\'autre.</string>
|
||||
<string name="tutorial_4_bottom">%1$s pour marquer la mine adjacente à 2️⃣ .</string>
|
||||
<string name="tutorial_5_top">Vérifier le nombre en surbrillance.</string>
|
||||
<string name="tutorial_5_bottom">%1$s pour ouvrir ou %2$s pour marquer.</string>
|
||||
|
@ -20,7 +20,7 @@
|
|||
<string name="beginner">Facile</string>
|
||||
<string name="intermediate">Intermédiaire</string>
|
||||
<string name="expert">Difficile</string>
|
||||
<string name="support_description">Avec votre aide, nous serons en mesure de implémenter de nouvelles fonctionnalités et de maintenir notre projet actif.</string>
|
||||
<string name="support_description">Avec votre aide, nous serons en mesure de mettre en place de nouvelles fonctionnalités et de maintenir notre projet actif.</string>
|
||||
<string name="open">Ouvrir</string>
|
||||
<string name="settings">Réglages</string>
|
||||
<string name="animations">Animations</string>
|
||||
|
@ -81,9 +81,9 @@
|
|||
<string name="retry">Réessayer</string>
|
||||
<string name="continue_game">Continuer</string>
|
||||
<string name="empty">Vide</string>
|
||||
<string name="cant_do_it_now">Impossible de faire cela maintenant</string>
|
||||
<string name="cant_do_it_now">Impossible de faire ça maintenant</string>
|
||||
<string name="you_have_received">Vous avez reçu : +%1$d</string>
|
||||
<string name="help_win_a_game">Pour plus d\'Aide, vous devez gagner une partie.</string>
|
||||
<string name="help_win_a_game">Pour plus d\'aide, vous devez gagner une partie.</string>
|
||||
<string name="unknown_error">Erreur inconnue.</string>
|
||||
<string name="leaderboards">Classements</string>
|
||||
<string name="cancel">Annuler</string>
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
<string name="you_won">Du vant!</string>
|
||||
<string name="victories">Seire</string>
|
||||
<string name="you_lost">Du tapte!</string>
|
||||
<string name="you_finished">You finished!</string>
|
||||
<string name="you_finished">Du er ferdig!</string>
|
||||
<string name="defeats">Nederlag</string>
|
||||
<string name="game_over_desc_1">Lykke til på det neste spillet.</string>
|
||||
<string name="game_over_desc_4">Du har avjobbet minefeltet i %1$d sekunder.</string>
|
||||
|
@ -79,7 +79,7 @@
|
|||
<string name="open_tile">Åpne</string>
|
||||
<string name="flag_tile">Flagge</string>
|
||||
<string name="retry">Prøv på nytt</string>
|
||||
<string name="continue_game">Continue</string>
|
||||
<string name="continue_game">Fortsett</string>
|
||||
<string name="empty">Tom</string>
|
||||
<string name="cant_do_it_now">Umulig å gjøre det nå</string>
|
||||
<string name="you_have_received">Du har mottatt: +%1$d</string>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<string name="tutorial_completed">Tutorial Concluído</string>
|
||||
<string name="tutorial_0_bottom">Para começar dê um %1$s em qualquer quadrado aleatório.</string>
|
||||
<string name="tutorial_1_top">Os números indicam quantas minas existem adjacentes ao quadrado destacado.</string>
|
||||
<string name="tutorial_1_bottom">Portanto, se o canto 1️⃣ tem apenas um quadrado adjacente, ele deve ter uma minha. Use %1$s para sinalizá-lo.</string>
|
||||
<string name="tutorial_1_bottom">Portanto, se o canto 1️⃣ tem apenas um quadrado adjacente, ele deve ter uma mina. Use %1$s para sinalizá-lo.</string>
|
||||
<string name="tutorial_2_top">O próximo quadrado fechado não tem uma mina porque o número é 1️⃣ , não 2️⃣ .</string>
|
||||
<string name="tutorial_2_bottom">Você pode abri-lo com %1$s.</string>
|
||||
<string name="tutorial_3_top">O mesmo acontece nesse 1️⃣ . Você sabe onde está a mina adjacente.</string>
|
||||
|
|
|
@ -8,5 +8,6 @@ interface IBillingManager {
|
|||
fun start()
|
||||
fun isEnabled(): Boolean
|
||||
suspend fun charge(activity: Activity)
|
||||
fun getPrice(): Flow<String>
|
||||
fun listenPurchases(): Flow<PurchaseInfo>
|
||||
}
|
||||
|
|
|
@ -2,4 +2,5 @@ package dev.lucasnlm.external
|
|||
|
||||
interface IFeatureFlagManager {
|
||||
fun isGameHistoryEnabled(): Boolean
|
||||
fun isRateUsEnabled(): Boolean
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Testez votre puissance de raisonnement sur un jeu de puzzle difficile où vous devez résoudre un champ de mines sans exploser aucune mine.
|
||||
Il s'agit d'une version minimaliste et moderne du Minesweeper classique.
|
||||
Testez votre puissance de raisonnement dans un jeu de puzzle difficile où vous devez nettoyer un champ de mines sans faire exploser aucune mine.
|
||||
Il s'agit d'une version minimaliste et moderne du Démineur classique.
|
||||
|
||||
Êtes-vous en mesure de nettoyer un champ de mines ? C'est parti !
|
||||
|
||||
|
@ -7,50 +7,50 @@ OBJECTIF
|
|||
C'est comme le Démineur. Vous gagnez la partie quand vous posez un drapeau sur chaque mine sur le champ de mine. Attention de ne pas en activer !
|
||||
|
||||
PAS BESOIN DE DEVINER
|
||||
Ce jeu a un algorithme qui crée toujours des champs de mines solvables. Vous n'aurez pas à deviner où se trouve la dernière mine !
|
||||
Ce jeu a un algorithme qui crée toujours des champs de mines résolubles. Vous n'aurez pas à deviner où se trouve la dernière mine !
|
||||
|
||||
NIVEAUX DE JEU
|
||||
Tout comme Minesweeper, ce jeu a différents niveaux: débutant, intermédiaire et expert. Mais il a aussi un mode progressif unique qui devient plus difficile quand vous gagnez.
|
||||
Tout comme le Démineur, ce jeu a différents niveaux : débutant, intermédiaire et expert. Mais il a aussi un mode progressif unique qui devient plus difficile quand vous gagnez.
|
||||
|
||||
NIVEAU PERSONNALISÉ
|
||||
Si les modes standards ne suffisent pas, vous pouvez créer votre niveau de difficulté !
|
||||
|
||||
THÉMES
|
||||
Antimine a plus de 15 thèmes (lumière, sombre et AMOLED). Et bientôt d'autres seront ajoutés!
|
||||
THÈMES
|
||||
Antimine a plus de 15 thèmes (incluant la lumière, l'obscurité et l'AMOLED). Et bientôt d'autres seront ajoutés !
|
||||
|
||||
MINIMALISTE
|
||||
Une apparence et une sensation de netteté : une taille visuelle minimaliste et aussi légère (moins de 9 Mo).
|
||||
|
||||
TUTORIEL
|
||||
Vous ne savez pas jouer au Démineur ? Pas de problème, vous pouvez jouer notre niveau de tutoriel et activer les conseils dans les paramètres.
|
||||
DIDACTICIEL
|
||||
Vous ne savez pas jouer au Démineur ? Pas de problème, vous pouvez jouer à notre niveau de didacticiel et activer les conseils dans les paramètres.
|
||||
|
||||
STATISTIQUES
|
||||
Vérifiez vos résultats et votre développement.
|
||||
Vérifiez vos résultats et votre progression.
|
||||
|
||||
ASSISTANT DU JEU
|
||||
Le jeu marquera automatiquement les carrés que vous avez isolés.
|
||||
|
||||
PERSONNALISATION
|
||||
Vous pouvez changer le jeu pour le faire comme vous le souhaitez.
|
||||
Exemple: aide, animations, retours haptiques, point d'interrogation.
|
||||
Vous pouvez modifier le jeu pour en faire ce que vous souhaitez.
|
||||
Exemple : aide, animations, retour tactile, point d'interrogation.
|
||||
|
||||
CONTRÔLES
|
||||
Il supporte quatre systèmes de contrôle différents. Exemple: drapeau en appuyant sur appui ou appui long.
|
||||
Il supporte quatre systèmes de contrôle différents. Exemple : drapeau en cliquant ou avec un clic prolongé.
|
||||
|
||||
ACHIEVEMENTS
|
||||
Vous avez huit réalisations à débloquer et différents classements à partager avec vos amis.
|
||||
SUCCÈS ET CLASSEMENTS
|
||||
Vous avez huit succès à débloquer et différents classements à partager avec vos amis.
|
||||
|
||||
ACCESSIBILITÉ
|
||||
Vous pouvez personnaliser ce jeu en fonction de vos besoins: changer la taille des carrés, le rayon, le minutage tactile et le support du lecteur d'écran.
|
||||
Vous pouvez personnaliser ce jeu en fonction de vos besoins : changer la taille des carrés, leur rayon, le minutage tactile et le support du lecteur d'écran.
|
||||
Nous essayons toujours d'améliorer nos paramètres d'accessibilité. Si vous manquez quelque chose, faites-nous part de vos commentaires !
|
||||
|
||||
ANDROID
|
||||
Il prend en charge les fonctionnalités cool d'Android comme l'écran partagé, les raccourcis, le partage, le thème du système et toujours le support de la dernière version.
|
||||
Il prend en charge les fonctionnalités cool d'Android comme l'écran partagé, les raccourcis, le partage, le thème du système et supporte toujours la dernière version.
|
||||
|
||||
EN LIGNE OU HORS LIGNE
|
||||
Vous n'avez pas besoin d'être en ligne pour jouer au jeu!
|
||||
Vous n'avez pas besoin d'être en ligne pour jouer au jeu !
|
||||
|
||||
SAUVEGARDE AUTO
|
||||
Vous pouvez commencer une partie et continuer plus tard! Il sauvegarder automatiquement votre partie.
|
||||
SAUVEGARDE AUTOMATIQUE
|
||||
Vous pouvez commencer une partie et la continuer plus tard ! Il sauvegardera automatiquement votre partie.
|
||||
|
||||
Profitez-en !
|
||||
|
|
|
@ -8,8 +8,8 @@ android {
|
|||
compileSdkVersion 30
|
||||
|
||||
defaultConfig {
|
||||
versionCode 805011
|
||||
versionName '8.5.1'
|
||||
versionCode 805021
|
||||
versionName '8.5.2'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ class BillingManager(
|
|||
activity.startActivity(intent)
|
||||
}
|
||||
|
||||
override fun getPrice(): Flow<String> = flowOf()
|
||||
|
||||
override fun listenPurchases(): Flow<PurchaseInfo> = flowOf()
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -2,4 +2,5 @@ package dev.lucasnlm.external
|
|||
|
||||
class FeatureFlagManager : IFeatureFlagManager {
|
||||
override fun isGameHistoryEnabled(): Boolean = true
|
||||
override fun isRateUsEnabled(): Boolean = false
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ android {
|
|||
compileSdkVersion 30
|
||||
|
||||
defaultConfig {
|
||||
versionCode 805011
|
||||
versionName '8.5.1'
|
||||
versionCode 805021
|
||||
versionName '8.5.2'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
}
|
||||
|
|
|
@ -14,7 +14,10 @@ import com.android.billingclient.api.querySkuDetails
|
|||
import dev.lucasnlm.external.model.PurchaseInfo
|
||||
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
|
||||
class BillingManager(
|
||||
private val context: Context,
|
||||
|
@ -22,6 +25,8 @@ class BillingManager(
|
|||
|
||||
private val purchaseBroadcaster = ConflatedBroadcastChannel<PurchaseInfo>()
|
||||
|
||||
private var unlockPrice = MutableStateFlow<String?>(null)
|
||||
|
||||
private val billingClient by lazy {
|
||||
BillingClient.newBuilder(context)
|
||||
.setListener(this)
|
||||
|
@ -29,6 +34,8 @@ class BillingManager(
|
|||
.build()
|
||||
}
|
||||
|
||||
override fun getPrice(): Flow<String> = unlockPrice.asSharedFlow().filterNotNull()
|
||||
|
||||
override fun listenPurchases(): Flow<PurchaseInfo> = purchaseBroadcaster.asFlow()
|
||||
|
||||
private fun handlePurchases(purchases: List<Purchase>) {
|
||||
|
@ -61,17 +68,23 @@ class BillingManager(
|
|||
}
|
||||
|
||||
override fun onBillingSetupFinished(billingResult: BillingResult) {
|
||||
val purchasesList: List<Purchase> = if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||
// The BillingClient is ready. You can query purchases here.
|
||||
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||
val skuDetailsParams = SkuDetailsParams.newBuilder()
|
||||
.setSkusList(listOf(BASIC_SUPPORT))
|
||||
.setType(BillingClient.SkuType.INAPP)
|
||||
.build()
|
||||
|
||||
billingClient.queryPurchases(BillingClient.SkuType.INAPP).purchasesList.let {
|
||||
it?.toList() ?: listOf()
|
||||
}
|
||||
} else {
|
||||
listOf()
|
||||
billingClient
|
||||
.querySkuDetailsAsync(skuDetailsParams) { _, list ->
|
||||
unlockPrice.tryEmit(list?.firstOrNull()?.price)
|
||||
}
|
||||
|
||||
val purchasesList: List<Purchase> = billingClient
|
||||
.queryPurchases(BillingClient.SkuType.INAPP)
|
||||
.purchasesList.let { it?.toList() ?: listOf() }
|
||||
|
||||
handlePurchases(purchasesList)
|
||||
}
|
||||
|
||||
handlePurchases(purchasesList)
|
||||
}
|
||||
|
||||
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList<Purchase>?) {
|
||||
|
@ -94,12 +107,12 @@ class BillingManager(
|
|||
}
|
||||
|
||||
override suspend fun charge(activity: Activity) {
|
||||
val skuDetailsParams = SkuDetailsParams.newBuilder()
|
||||
.setSkusList(listOf(BASIC_SUPPORT))
|
||||
.setType(BillingClient.SkuType.INAPP)
|
||||
.build()
|
||||
|
||||
if (billingClient.isReady) {
|
||||
val skuDetailsParams = SkuDetailsParams.newBuilder()
|
||||
.setSkusList(listOf(BASIC_SUPPORT))
|
||||
.setType(BillingClient.SkuType.INAPP)
|
||||
.build()
|
||||
|
||||
val details = billingClient.querySkuDetails(skuDetailsParams)
|
||||
details.skuDetailsList?.firstOrNull()?.let {
|
||||
val flowParams = BillingFlowParams.newBuilder()
|
||||
|
|
|
@ -2,4 +2,5 @@ package dev.lucasnlm.external
|
|||
|
||||
class FeatureFlagManager : IFeatureFlagManager {
|
||||
override fun isGameHistoryEnabled(): Boolean = false
|
||||
override fun isRateUsEnabled(): Boolean = true
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
// versionCode and versionName must be hardcoded to support F-droid
|
||||
versionCode 805011
|
||||
versionName '8.5.1'
|
||||
versionCode 805021
|
||||
versionName '8.5.2'
|
||||
applicationId 'dev.lucasnlm.antimine'
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 30
|
||||
|
|
|
@ -58,8 +58,15 @@ class WatchLevelFragment : CommonLevelFragment(R.layout.fragment_level) {
|
|||
eventObserver.observe(
|
||||
viewLifecycleOwner,
|
||||
{
|
||||
if (it == Event.StartNewGame) {
|
||||
recyclerGrid.scrollToPosition(areaAdapter.itemCount / 2)
|
||||
if (!gameViewModel.hasPlantedMines()) {
|
||||
recyclerGrid.post {
|
||||
levelSetup.value?.let { minefield ->
|
||||
val size = dimensionRepository.areaSizeWithPadding()
|
||||
val dx = minefield.width * size * 0.25f
|
||||
val dy = minefield.height * size * 0.25f
|
||||
recyclerGrid.smoothScrollBy(dx.toInt(), dy.toInt(), null, 200)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (it) {
|
||||
|
|
Loading…
Reference in a new issue