First funcional game control change

This commit is contained in:
Lucas Lima 2020-06-27 20:13:30 -03:00
parent 9a449d1bc1
commit feb16dd3a3
No known key found for this signature in database
GPG key ID: 0259A3F43EC1027A
42 changed files with 464 additions and 96 deletions

View file

@ -94,12 +94,13 @@ dependencies {
// Dagger
implementation 'com.google.dagger:hilt-android:2.28.1-alpha'
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
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-alpha01"
kapt "androidx.hilt:hilt-compiler:1.0.0-alpha01"
// Kotlin
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5'

View file

@ -1,6 +1,7 @@
package dev.lucasnlm.antimine
import android.content.ActivityNotFoundException
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@ -28,6 +29,7 @@ import dev.lucasnlm.antimine.common.level.models.Score
import dev.lucasnlm.antimine.common.level.models.Status
import dev.lucasnlm.antimine.common.level.repository.ISavesRepository
import dev.lucasnlm.antimine.common.level.viewmodel.GameViewModel
import dev.lucasnlm.antimine.control.ControlDialogFragment
import dev.lucasnlm.antimine.core.analytics.AnalyticsManager
import dev.lucasnlm.antimine.core.analytics.models.Analytics
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
@ -46,7 +48,7 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint
class GameActivity : AppCompatActivity() {
class GameActivity : AppCompatActivity(), DialogInterface.OnDismissListener {
@Inject
lateinit var preferencesRepository: IPreferencesRepository
@ -162,6 +164,7 @@ class GameActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
if (status == Status.Running) {
viewModel.updateGameControl()
viewModel.resumeGame()
analyticsManager.sentEvent(Analytics.Resume())
}
@ -264,6 +267,7 @@ class GameActivity : AppCompatActivity() {
R.id.intermediate -> changeDifficulty(Difficulty.Intermediate)
R.id.expert -> changeDifficulty(Difficulty.Expert)
R.id.custom -> showCustomLevelDialog()
R.id.control -> showControlDialog()
R.id.about -> showAbout()
R.id.settings -> showSettings()
R.id.rate -> openRateUsLink("Drawer")
@ -379,6 +383,18 @@ class GameActivity : AppCompatActivity() {
}
}
private fun showControlDialog() {
if (status == Status.Running) {
viewModel.pauseGame()
}
if (supportFragmentManager.findFragmentByTag(CustomLevelDialogFragment.TAG) == null) {
ControlDialogFragment().apply {
show(supportFragmentManager, ControlDialogFragment.TAG)
}
}
}
private fun showAbout() {
analyticsManager.sentEvent(Analytics.OpenAbout())
Intent(this, AboutActivity::class.java).apply {
@ -514,8 +530,7 @@ class GameActivity : AppCompatActivity() {
*/
private fun restartIfNeed() {
if (usingLargeArea != preferencesRepository.useLargeAreas()) {
finish()
Intent(this, GameActivity::class.java).run { startActivity(this) }
recreate()
}
}
@ -567,6 +582,13 @@ class GameActivity : AppCompatActivity() {
}
}
override fun onDismiss(dialog: DialogInterface?) {
if (status == Status.Running) {
viewModel.updateGameControl()
viewModel.resumeGame()
}
}
companion object {
const val PREFERENCE_FIRST_USE = "preference_first_use"
const val PREFERENCE_USE_COUNT = "preference_use_count"

View file

@ -30,13 +30,13 @@ class AboutActivity : AppCompatActivity() {
Observer { event ->
when (event) {
AboutEvent.ThirdPartyLicenses -> {
replaceFragment(ThirdPartiesFragment(), ThirdPartiesFragment::class.simpleName)
replaceFragment(ThirdPartiesFragment(), ThirdPartiesFragment.TAG)
}
AboutEvent.SourceCode -> {
openSourceCode()
}
AboutEvent.Translators -> {
replaceFragment(TranslatorsFragment(), TranslatorsFragment::class.simpleName)
replaceFragment(TranslatorsFragment(), TranslatorsFragment.TAG)
}
else -> {
replaceFragment(AboutInfoFragment(), null)

View file

@ -48,4 +48,8 @@ class ThirdPartiesFragment : Fragment() {
licenses.adapter = aboutViewModel?.getLicenses()
}
companion object {
const val TAG = "ThirdPartiesFragment"
}
}

View file

@ -40,4 +40,8 @@ class TranslatorsFragment : Fragment() {
adapter = aboutViewModel?.getTranslators()
}
}
companion object {
const val TAG = "TranslatorsFragment"
}
}

View file

@ -0,0 +1,89 @@
package dev.lucasnlm.antimine.control
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDialogFragment
import dagger.hilt.android.AndroidEntryPoint
import dev.lucasnlm.antimine.R
import dev.lucasnlm.antimine.control.view.ControlItemView
import dev.lucasnlm.antimine.control.viewmodel.ControlViewModel
import dev.lucasnlm.antimine.core.control.ControlStyle
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
import dev.lucasnlm.antimine.core.viewModels
import javax.inject.Inject
@AndroidEntryPoint
class ControlDialogFragment : AppCompatDialogFragment() {
@Inject
lateinit var preferencesRepository: IPreferencesRepository
private val controlViewModel by viewModels<ControlViewModel>()
private val adapter by lazy { ControlListAdapter(controlViewModel) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter.setList(controlViewModel.gameControlOptions)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val currentControl = preferencesRepository.controlType().ordinal
return AlertDialog.Builder(requireContext(), R.style.MyDialog).apply {
setTitle(R.string.control)
setSingleChoiceItems(adapter, currentControl, null)
setPositiveButton(android.R.string.ok, null) // TODO OK
}.create()
}
override fun onDismiss(dialog: DialogInterface) {
if (activity is DialogInterface.OnDismissListener) {
(activity as DialogInterface.OnDismissListener).onDismiss(dialog)
}
super.onDismiss(dialog)
}
class ControlListAdapter(
private val controlViewModel: ControlViewModel
) : BaseAdapter() {
private var selected = controlViewModel.controlTypeSelected.value
private var controlList = listOf<ControlStyle>()
fun setList(list: List<ControlStyle>) {
controlList = list
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val view = if (convertView == null) {
ControlItemView(parent!!.context)
} else {
(convertView as ControlItemView)
}
return view.apply {
setRadio(selected == controlList[position])
setOnClickListener {
controlViewModel.selectControlType(controlList[position])
selected = controlList[position]
notifyDataSetChanged()
}
}
}
override fun hasStableIds(): Boolean = true
override fun getItem(position: Int): Any = controlList[position]
override fun getItemId(position: Int): Long = controlList[position].ordinal.toLong()
override fun getCount(): Int = controlList.count()
}
companion object {
const val TAG = "ControlDialogFragment"
}
}

View file

@ -0,0 +1,5 @@
package dev.lucasnlm.antimine.control.model
data class ControlModel(
val id: Long
)

View file

@ -0,0 +1,37 @@
package dev.lucasnlm.antimine.control.view
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.appcompat.widget.AppCompatRadioButton
import dev.lucasnlm.antimine.R
class ControlItemView : 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)
private val radio: AppCompatRadioButton
private val root: View
init {
LayoutInflater
.from(context)
.inflate(R.layout.view_control_item, this, true)
radio = findViewById(R.id.radio)
root = findViewById(R.id.root)
}
fun setRadio(selected: Boolean) {
radio.isChecked = selected
}
override fun setOnClickListener(listener: OnClickListener?) {
super.setOnClickListener(listener)
root.setOnClickListener(listener)
radio.setOnClickListener(listener)
}
}

View file

@ -0,0 +1,26 @@
package dev.lucasnlm.antimine.control.viewmodel
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import dev.lucasnlm.antimine.core.control.ControlStyle
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
class ControlViewModel @ViewModelInject constructor(
private val preferencesRepository: IPreferencesRepository
) : ViewModel() {
val controlTypeSelected = MutableLiveData<ControlStyle>(preferencesRepository.controlType())
val gameControlOptions = listOf(
ControlStyle.Standard, ControlStyle.FastFlag, ControlStyle.DoubleClick
)
init {
controlTypeSelected.postValue(preferencesRepository.controlType())
}
fun selectControlType(controlStyle: ControlStyle) {
preferencesRepository.useControlType(controlStyle)
controlTypeSelected.postValue(controlStyle)
}
}

View file

@ -88,6 +88,6 @@ class CustomLevelDialogFragment : AppCompatDialogFragment() {
const val MIN_HEIGHT = 5
const val MIN_MINES = 3
val TAG = CustomLevelDialogFragment::class.simpleName
const val TAG = "CustomLevelDialogFragment"
}
}

View file

@ -153,6 +153,6 @@ class EndGameDialogFragment : AppCompatDialogFragment() {
private const val DIALOG_TOTAL_MINES = "dialog_total_mines"
private const val DIALOG_SAVE_ID = "dialog_save_id"
val TAG = EndGameDialogFragment::class.simpleName
const val TAG = "EndGameDialogFragment"
}
}

View file

@ -0,0 +1,74 @@
<?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"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:clickable="true"
android:focusable="true"
android:paddingHorizontal="16dp">
<androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/radio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
app:layout_constraintBottom_toBottomOf="@+id/standard_details"
app:layout_constraintEnd_toStartOf="@id/standard_details"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TableLayout
android:id="@+id/standard_details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/standard"
app:layout_constraintTop_toTopOf="parent">
<TableRow>
<TextView
android:padding="4dp"
android:text="@string/standard"
android:textColor="@color/black"
android:textStyle="bold" />
</TableRow>
<TableRow>
<TextView
android:padding="4dp"
android:text="Single Tap"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:text="@string/arrow" />
<TextView
android:gravity="right"
android:padding="4dp"
android:text="Open Area" />
</TableRow>
<TableRow>
<TextView
android:padding="4dp"
android:text="Long Tap"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:text="@string/arrow" />
<TextView
android:gravity="right"
android:padding="4dp"
android:text="Put Flag" />
</TableRow>
</TableLayout>
</LinearLayout>

View file

@ -5,7 +5,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?android:attr/selectableItemBackground"
android:background="?android:attr/selectableItemBackground"
tools:targetApi="m">
<TextView

View file

@ -37,9 +37,7 @@
</menu>
</item>
<group
android:id="@+id/history_group">
<group android:id="@+id/history_group">
<item
android:id="@+id/previous_games"
android:checkable="false"
@ -49,7 +47,18 @@
android:id="@+id/stats"
android:checkable="false"
android:title="@string/events" />
</group>
<group android:id="@+id/options_group">
<item
android:id="@+id/control"
android:checkable="false"
android:title="@string/control" />
<item
android:id="@+id/settings"
android:checkable="false"
android:title="@string/settings" />
</group>
<group
@ -62,12 +71,9 @@
android:checkable="false"
android:icon="@drawable/install"
android:title="@string/install" />
</group>
<group
android:id="@+id/social_group">
<group android:id="@+id/social_group">
<item
android:id="@+id/share_now"
android:checkable="false"
@ -79,20 +85,12 @@
android:id="@+id/rate"
android:checkable="false"
android:title="@string/rating_menu" />
</group>
<group>
<item
android:id="@+id/settings"
android:checkable="false"
android:title="@string/settings" />
<group android:id="@+id/about_group">
<item
android:id="@+id/about"
android:checkable="false"
android:title="@string/about" />
</group>
</menu>

View file

@ -6,7 +6,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.2'
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28.1-alpha'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.71'
}

View file

@ -55,13 +55,13 @@ dependencies {
// Dagger
implementation 'com.google.dagger:hilt-android:2.28.1-alpha'
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
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"
// Room
api 'androidx.room:room-runtime:2.2.5'
api 'androidx.room:room-ktx:2.2.5'

View file

@ -9,7 +9,7 @@ import dev.lucasnlm.antimine.common.level.models.Difficulty
import dev.lucasnlm.antimine.common.level.models.Mark
import dev.lucasnlm.antimine.common.level.models.Minefield
import dev.lucasnlm.antimine.common.level.models.Score
import dev.lucasnlm.antimine.core.control.Action
import dev.lucasnlm.antimine.core.control.ActionResponse
import dev.lucasnlm.antimine.core.control.ActionFeedback
import dev.lucasnlm.antimine.core.control.GameControl
import kotlin.math.floor
@ -24,7 +24,7 @@ class GameController {
private val startTime = System.currentTimeMillis()
private var saveId = 0
private var firstOpen: FirstOpen = FirstOpen.Unknown
private val gameControl: GameControl = GameControl.Standard
private var gameControl: GameControl = GameControl.Standard
private var mines: Sequence<Area> = emptySequence()
var hasMines = false
@ -264,15 +264,19 @@ class GameController {
this.saveId = id.coerceAtLeast(0)
}
fun updateGameControl(newGameControl: GameControl) {
this.gameControl = newGameControl
}
/**
* Run a game [action] on a given tile.
* Run a game [actionResponse] on a given tile.
* @return The number of changed tiles.
*/
private fun Area.runActionOn(action: Action?): ActionFeedback {
private fun Area.runActionOn(actionResponse: ActionResponse?): ActionFeedback {
val highlightedChanged = turnOffAllHighlighted()
val changed = when (action) {
Action.OpenTile -> {
val changed = when (actionResponse) {
ActionResponse.OpenTile -> {
if (!hasMines) {
plantMinesExcept(id, true)
}
@ -284,21 +288,21 @@ class GameController {
openTile()
}
}
Action.SwitchMark -> {
ActionResponse.SwitchMark -> {
if (isCovered) switchMark()
1
}
Action.HighlightNeighbors -> {
ActionResponse.HighlightNeighbors -> {
if (minesAround != 0) highlight() else 0
}
Action.OpenNeighbors -> {
ActionResponse.OpenNeighbors -> {
openNeighbors()
8
}
else -> 0
}
return ActionFeedback(action, id, (changed + highlightedChanged) > 1)
return ActionFeedback(actionResponse, id, (changed + highlightedChanged) > 1)
}
fun Area.switchMark(): Area = apply {

View file

@ -142,7 +142,7 @@ class AreaAdapter(
}
companion object {
private val TAG = AreaAdapter::class.simpleName
const val TAG = "AreaAdapter"
fun createAreaPaintSettings(context: Context, useLargeArea: Boolean): AreaPaintSettings {
val resources = context.resources

View file

@ -82,7 +82,7 @@ class AreaView : View {
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent?): Boolean =
(gestureDetector?.onTouchEvent(event) ?: false) || super.onTouchEvent(event)
gestureDetector.onTouchEvent(event) || super.onTouchEvent(event)
private fun bindContentDescription(area: Area) {
contentDescription = when {

View file

@ -19,8 +19,9 @@ import dev.lucasnlm.antimine.common.level.utils.Clock
import dev.lucasnlm.antimine.common.level.utils.IHapticFeedbackInteractor
import dev.lucasnlm.antimine.core.analytics.AnalyticsManager
import dev.lucasnlm.antimine.core.analytics.models.Analytics
import dev.lucasnlm.antimine.core.control.Action
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@ -61,6 +62,7 @@ class GameViewModel @ViewModelInject constructor(
)
gameController = GameController(minefield, minefieldRepository.randomSeed())
updateGameControl()
mineCount.postValue(minefield.mines)
difficulty.postValue(newDifficulty)
@ -86,6 +88,7 @@ class GameViewModel @ViewModelInject constructor(
val setup = save.minefield
gameController = GameController(save)
updateGameControl()
mineCount.postValue(setup.mines)
difficulty.postValue(save.difficulty)
@ -114,6 +117,7 @@ class GameViewModel @ViewModelInject constructor(
plantMinesExcept(save.firstOpen.value, true)
singleClick(save.firstOpen.value)
}
updateGameControl()
}
mineCount.postValue(setup.mines)
@ -252,11 +256,19 @@ class GameViewModel @ViewModelInject constructor(
}
private fun onFeedbackAnalytics(feedback: ActionFeedback) {
when (feedback.action) {
Action.OpenTile -> { analyticsManager.sentEvent(Analytics.OpenTile(feedback.index)) }
Action.SwitchMark -> { analyticsManager.sentEvent(Analytics.SwitchMark(feedback.index)) }
Action.HighlightNeighbors -> { analyticsManager.sentEvent(Analytics.HighlightNeighbors(feedback.index)) }
Action.OpenNeighbors -> { analyticsManager.sentEvent(Analytics.OpenNeighbors(feedback.index)) }
when (feedback.actionResponse) {
ActionResponse.OpenTile -> {
analyticsManager.sentEvent(Analytics.OpenTile(feedback.index))
}
ActionResponse.SwitchMark -> {
analyticsManager.sentEvent(Analytics.SwitchMark(feedback.index))
}
ActionResponse.HighlightNeighbors -> {
analyticsManager.sentEvent(Analytics.HighlightNeighbors(feedback.index))
}
ActionResponse.OpenNeighbors -> {
analyticsManager.sentEvent(Analytics.OpenNeighbors(feedback.index))
}
}
}
@ -283,6 +295,12 @@ class GameViewModel @ViewModelInject constructor(
}
}
fun updateGameControl() {
val controlType = preferencesRepository.controlType()
val gameControl = GameControl.fromControlType(controlType)
gameController.updateGameControl(gameControl)
}
fun runClock() {
clock.run {
if (isStopped) start {

View file

@ -0,0 +1,18 @@
package dev.lucasnlm.antimine.core
import androidx.annotation.MainThread
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelLazy
import androidx.lifecycle.ViewModelProvider
@MainThread
inline fun <reified VM : ViewModel> Fragment.viewModels(
noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}

View file

@ -14,6 +14,6 @@ class DebugAnalyticsManager : AnalyticsManager {
}
companion object {
val TAG = DebugAnalyticsManager::class.simpleName
const val TAG = "DebugAnalyticsManager"
}
}

View file

@ -1,64 +1,101 @@
package dev.lucasnlm.antimine.core.control
enum class Action {
/**
* Possible action response to an user action.
*/
enum class ActionResponse {
OpenTile,
SwitchMark,
HighlightNeighbors,
OpenNeighbors,
}
/**
* [Actions] links an [ActionResponse] to an user action.
*/
data class Actions(
val singleClick: Action?,
val doubleClick: Action?,
val longPress: Action?
val singleClick: ActionResponse?,
val doubleClick: ActionResponse?,
val longPress: ActionResponse?
)
/**
* These are the current available game control styles.
* Check [GameControl] to details.
*/
enum class ControlStyle {
Standard,
DoubleClick,
FastFlag
}
/**
* [GameControl] will map an user action (from [Actions]) to an [ActionResponse].
* This is necessary because same users rather that single click open the tile, other that it flags the tile.
*/
sealed class GameControl(
val id: ControlStyle,
val onCovered: Actions,
val onOpen: Actions
) {
object Standard : GameControl(
id = ControlStyle.Standard,
onCovered = Actions(
singleClick = Action.OpenTile,
longPress = Action.SwitchMark,
singleClick = ActionResponse.OpenTile,
longPress = ActionResponse.SwitchMark,
doubleClick = null
),
onOpen = Actions(
singleClick = Action.HighlightNeighbors,
longPress = Action.OpenNeighbors,
singleClick = ActionResponse.HighlightNeighbors,
longPress = ActionResponse.OpenNeighbors,
doubleClick = null
)
)
object FastFlag : GameControl(
id = ControlStyle.FastFlag,
onCovered = Actions(
singleClick = Action.SwitchMark,
longPress = Action.OpenTile,
singleClick = ActionResponse.SwitchMark,
longPress = ActionResponse.OpenTile,
doubleClick = null
),
onOpen = Actions(
singleClick = Action.OpenNeighbors,
longPress = Action.HighlightNeighbors,
singleClick = ActionResponse.OpenNeighbors,
longPress = ActionResponse.HighlightNeighbors,
doubleClick = null
)
)
object DoubleClick : GameControl(
id = ControlStyle.DoubleClick,
onCovered = Actions(
singleClick = Action.SwitchMark,
singleClick = ActionResponse.SwitchMark,
longPress = null,
doubleClick = Action.OpenTile
doubleClick = ActionResponse.OpenTile
),
onOpen = Actions(
singleClick = Action.HighlightNeighbors,
singleClick = ActionResponse.HighlightNeighbors,
longPress = null,
doubleClick = Action.OpenNeighbors
doubleClick = ActionResponse.OpenNeighbors
)
)
companion object {
fun fromControlType(controlStyle: ControlStyle): GameControl {
return when (controlStyle) {
ControlStyle.Standard -> Standard
ControlStyle.DoubleClick -> DoubleClick
ControlStyle.FastFlag -> FastFlag
}
}
}
}
/**
* A data class used to make feedback or analytics to an user action.
*/
data class ActionFeedback(
val action: Action?,
val actionResponse: ActionResponse?,
val index: Int,
val multipleChanges: Boolean
)

View file

@ -9,7 +9,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dev.lucasnlm.antimine.core.analytics.AnalyticsManager
import dev.lucasnlm.antimine.core.analytics.DebugAnalyticsManager
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
import dev.lucasnlm.antimine.core.preferences.PreferencesInteractor
import dev.lucasnlm.antimine.core.preferences.PreferencesManager
import dev.lucasnlm.antimine.core.preferences.PreferencesRepository
import javax.inject.Singleton
@ -19,14 +19,14 @@ class CommonModule {
@Singleton
@Provides
fun providePreferencesRepository(
preferencesInteractor: PreferencesInteractor
): IPreferencesRepository = PreferencesRepository(preferencesInteractor)
preferencesManager: PreferencesManager
): IPreferencesRepository = PreferencesRepository(preferencesManager)
@Singleton
@Provides
fun providePreferencesInteractor(
@ApplicationContext context: Context
): PreferencesInteractor = PreferencesInteractor(context)
): PreferencesManager = PreferencesManager(context)
@Singleton
@Provides

View file

@ -5,7 +5,7 @@ import androidx.preference.PreferenceManager
import dev.lucasnlm.antimine.common.level.models.Minefield
import javax.inject.Inject
class PreferencesInteractor @Inject constructor(
class PreferencesManager @Inject constructor(
private val context: Context
) {
private val preferences by lazy {
@ -34,6 +34,10 @@ class PreferencesInteractor @Inject constructor(
fun putInt(key: String, value: Int) = preferences.edit().putInt(key, value).apply()
fun removeKey(key: String) {
preferences.edit().remove(key).apply()
}
companion object {
private const val PREFERENCE_CUSTOM_GAME_WIDTH = "preference_custom_game_width"
private const val PREFERENCE_CUSTOM_GAME_HEIGHT = "preference_custom_game_height"

View file

@ -1,43 +1,50 @@
package dev.lucasnlm.antimine.core.preferences
import dev.lucasnlm.antimine.common.level.models.Minefield
import dev.lucasnlm.antimine.core.control.ControlStyle
interface IPreferencesRepository {
fun customGameMode(): Minefield
fun updateCustomGameMode(minefield: Minefield)
fun getBoolean(key: String, defaultValue: Boolean): Boolean
fun getInt(key: String, defaultValue: Int): Int
fun putBoolean(key: String, value: Boolean)
fun putInt(key: String, value: Int)
fun customGameMode(): Minefield
fun updateCustomGameMode(minefield: Minefield)
fun controlType(): ControlStyle
fun useControlType(controlStyle: ControlStyle)
fun useFlagAssistant(): Boolean
fun useHapticFeedback(): Boolean
fun useLargeAreas(): Boolean
fun useAnimations(): Boolean
fun useDoubleClickToOpen(): Boolean
}
class PreferencesRepository(
private val preferencesInteractor: PreferencesInteractor
private val preferencesManager: PreferencesManager
) : IPreferencesRepository {
init {
migrateOldPreferences()
}
override fun customGameMode(): Minefield =
preferencesInteractor.getCustomMode()
preferencesManager.getCustomMode()
override fun updateCustomGameMode(minefield: Minefield) =
preferencesInteractor.updateCustomMode(minefield)
preferencesManager.updateCustomMode(minefield)
override fun getBoolean(key: String, defaultValue: Boolean): Boolean =
preferencesInteractor.getBoolean(key, defaultValue)
preferencesManager.getBoolean(key, defaultValue)
override fun putBoolean(key: String, value: Boolean) =
preferencesInteractor.putBoolean(key, value)
preferencesManager.putBoolean(key, value)
override fun getInt(key: String, defaultValue: Int): Int =
preferencesInteractor.getInt(key, defaultValue)
preferencesManager.getInt(key, defaultValue)
override fun putInt(key: String, value: Int) =
preferencesInteractor.putInt(key, value)
preferencesManager.putInt(key, value)
override fun useFlagAssistant(): Boolean =
getBoolean("preference_assistant", true)
@ -51,6 +58,19 @@ class PreferencesRepository(
override fun useAnimations(): Boolean =
getBoolean("preference_animation", true)
override fun useDoubleClickToOpen(): Boolean =
getBoolean("preference_double_click_open", false)
override fun controlType(): ControlStyle {
val index = getInt("preference_control_type", -1)
return ControlStyle.values().getOrNull(index) ?: ControlStyle.Standard
}
override fun useControlType(controlStyle: ControlStyle) {
putInt("preference_control_type", controlStyle.ordinal)
}
private fun migrateOldPreferences() {
if (getBoolean("preference_double_click_open", false)) {
useControlType(ControlStyle.DoubleClick)
preferencesManager.removeKey("preference_double_click_open")
}
}
}

View file

@ -91,7 +91,7 @@
<string name="settings_accessibility">Accessibility</string>
<string name="settings_large_areas">Use Large Areas</string>
<string name="settings_large_areas_desc">Increases the touch area</string>
<string name="settings_double_click">Double Click</string>
<string name="double_click">Double Click</string>
<string name="rating">Feedback</string>
<string name="rating_menu">Feedback ❤</string>
<string name="rating_message">If you like this game, please give us a feedback. It will help us a lot.</string>

View file

@ -91,7 +91,7 @@
<string name="settings_accessibility">Přístupnost</string>
<string name="settings_large_areas">Použít velké oblasti</string>
<string name="settings_large_areas_desc">Zvyšuje dotykovou oblast</string>
<string name="settings_double_click">Poklepání</string>
<string name="double_click">Poklepání</string>
<string name="rating">Komentář</string>
<string name="rating_menu">Komentář ❤</string>
<string name="rating_message">Pokud se vám tato hra líbí, dejte nám prosím zpětnou vazbu. Hodně nám to pomůže.</string>

View file

@ -91,7 +91,7 @@
<string name="settings_accessibility">Barrierefreiheit</string>
<string name="settings_large_areas">Große Flächen verwenden</string>
<string name="settings_large_areas_desc">Vergrößert die Tastfläche</string>
<string name="settings_double_click">Doppelklick</string>
<string name="double_click">Doppelklick</string>
<string name="rating">Rückmeldung</string>
<string name="rating_menu">Rückmeldung ❤</string>
<string name="rating_message">Wenn dir dieses Spiel gefällt, gib uns bitte eine Rückmeldung. Es wird uns sehr helfen.</string>

View file

@ -91,7 +91,7 @@
<string name="settings_accessibility">Προσβασιμότητα</string>
<string name="settings_large_areas">Χρησιμοποιήστε μεγάλες περιοχές</string>
<string name="settings_large_areas_desc">Αύξηση της περιοχής κουμπιών</string>
<string name="settings_double_click">Διπλό Κλικ</string>
<string name="double_click">Διπλό Κλικ</string>
<string name="rating">Αξιολόγηση</string>
<string name="rating_menu">Αξιολόγηση ❤</string>
<string name="rating_message">Αν σας αρέσει αυτό το παιχνίδι, παρακαλούμε δώστε μας τα σχόλιά σας. Θα μας βοηθήσει πολύ.</string>

View file

@ -91,7 +91,7 @@
<string name="settings_accessibility">Accesibilidad</string>
<string name="settings_large_areas">Usar áreas grandes</string>
<string name="settings_large_areas_desc">Aumenta la zona táctil</string>
<string name="settings_double_click">Doble clic</string>
<string name="double_click">Doble clic</string>
<string name="rating">Comentarios</string>
<string name="rating_menu">Comentarios ❤</string>
<string name="rating_message">Si te gusta este juego, por favor danos un comentario. Nos ayudará mucho.</string>

View file

@ -91,7 +91,7 @@
<string name="settings_accessibility">Accessibilité</string>
<string name="settings_large_areas">Utiliser des grandes cases</string>
<string name="settings_large_areas_desc">Augmenter la zone d\'interaction</string>
<string name="settings_double_click">Cliquer deux fois</string>
<string name="double_click">Cliquer deux fois</string>
<string name="rating">Retour d\'expérience</string>
<string name="rating_menu">Retour d\'expérience ❤️</string>
<string name="rating_message">Si vous aimez ce jeu, n\'hésitez pas à jour donner un retour. Ça nous serait très utile.</string>

View file

@ -91,7 +91,7 @@
<string name="settings_accessibility">Accessibilità</string>
<string name="settings_large_areas">Usa grandi aree</string>
<string name="settings_large_areas_desc">Aumenta l\'area di tocco</string>
<string name="settings_double_click">Doppio click</string>
<string name="double_click">Doppio click</string>
<string name="rating">Suggerimenti</string>
<string name="rating_menu">Suggerimenti ❤</string>
<string name="rating_message">Se ti piace questo gioco, per favore inviaci suggerimenti. Puoi aiutare a migliorarlo.</string>

View file

@ -91,7 +91,7 @@
<string name="settings_accessibility">アクセシビリティ</string>
<string name="settings_large_areas">範囲を広くする</string>
<string name="settings_large_areas_desc">タッチ範囲を増やします</string>
<string name="settings_double_click">ダブルクリック</string>
<string name="double_click">ダブルクリック</string>
<string name="rating">フィードバック</string>
<string name="rating_menu">フィードバック ❤</string>
<string name="rating_message">もしこのゲームを気に入ったなら、フィードバックをお願いします。私たちへの助けになるはずです。</string>

View file

@ -91,7 +91,7 @@
<string name="settings_accessibility">Acessibilidade</string>
<string name="settings_large_areas">Usar Área Grande</string>
<string name="settings_large_areas_desc">Aumenta a área tocável</string>
<string name="settings_double_click">Duplo Clique</string>
<string name="double_click">Duplo Clique</string>
<string name="rating">Avalie o app</string>
<string name="rating_menu">Avaliar o app ❤</string>
<string name="rating_message">Se você está gostando do jogo, por favor deixe um comentário! Isso nos ajuda muito.</string>

View file

@ -91,7 +91,7 @@
<string name="settings_accessibility">Специальные возможности</string>
<string name="settings_large_areas">Использовать большие области</string>
<string name="settings_large_areas_desc">Увеличивает зону касания</string>
<string name="settings_double_click">Два нажатия</string>
<string name="double_click">Два нажатия</string>
<string name="rating">Обратная связь</string>
<string name="rating_menu">Обратная связь ❤</string>
<string name="rating_message">Нравится игра? Оставьте отзыв, пожалуйста. Это нам очень поможет.</string>

View file

@ -91,7 +91,7 @@
<string name="settings_accessibility">Erişilebilirlik</string>
<string name="settings_large_areas">Geniş Alanlar Kullan</string>
<string name="settings_large_areas_desc">Dokunma alanını artırır</string>
<string name="settings_double_click">Çift Tıklama</string>
<string name="double_click">Çift Tıklama</string>
<string name="rating">Geri bildirim</string>
<string name="rating_menu">Geri bildirim ❤</string>
<string name="rating_message">Bu oyunu beğendiyseniz, lütfen bize bir geri bildirim verin. Bize çok yardımcı olacak.</string>

View file

@ -91,7 +91,7 @@
<string name="settings_accessibility">Спеціальні можливості</string>
<string name="settings_large_areas">Використовувати збільшену територію</string>
<string name="settings_large_areas_desc">Збільшує площу дотику</string>
<string name="settings_double_click">Подвійний клік</string>
<string name="double_click">Подвійний клік</string>
<string name="rating">Відгук</string>
<string name="rating_menu">Відгук ❤</string>
<string name="rating_message">Якщо вам подобається ця гра, то залиште нам свій відгук. Це нам дуже допоможе.</string>

View file

@ -91,7 +91,7 @@
<string name="settings_accessibility">Trợ năng</string>
<string name="settings_large_areas">Vùng chạm lớn</string>
<string name="settings_large_areas_desc">Tăng kích cỡ các vùng chạm</string>
<string name="settings_double_click">Nháy kép</string>
<string name="double_click">Nháy kép</string>
<string name="rating">Phản hồi</string>
<string name="rating_menu">Phản hồi ❤</string>
<string name="rating_message">Nếu bạn thích trò chơi này, hãy gửi phản hồi cho chúng tôi. Nhận xét của bạn sẽ giúp chúng tôi rất nhiều.</string>

View file

@ -91,7 +91,7 @@
<string name="settings_accessibility">无障碍</string>
<string name="settings_large_areas">使用大区域</string>
<string name="settings_large_areas_desc">增加触摸区域</string>
<string name="settings_double_click">双击</string>
<string name="double_click">双击</string>
<string name="rating">反馈</string>
<string name="rating_menu">反馈 ❤</string>
<string name="rating_message">如果你喜欢这个游戏,请给我们反馈。这将对我们有很多帮助。</string>

View file

@ -91,7 +91,6 @@
<string name="settings_accessibility">Accessibility</string>
<string name="settings_large_areas">Use Large Areas</string>
<string name="settings_large_areas_desc">Increases the touch area</string>
<string name="settings_double_click">Double Click</string>
<string name="rating">Feedback</string>
<string name="rating_menu">Feedback ❤</string>
<string name="rating_message">If you like this game, please give us a feedback. It will help us a lot.</string>
@ -101,4 +100,12 @@
<string name="total_time">Total Time</string>
<string name="average_time">Average Time</string>
<string name="performance">Performance</string>
<string name="control">Control</string>
<string name="arrow"></string>
<string name="single_click">Single Click</string>
<string name="double_click">Double Click</string>
<string name="long_press">Long Press</string>
<string name="open_tile">Open Area</string>
</resources>

View file

@ -11,7 +11,7 @@
android:checked="false"
android:defaultValue="false"
android:key="preference_double_click_open"
android:title="@string/settings_double_click"
android:title="@string/double_click"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat