commit
4c583f6a22
14 changed files with 162 additions and 32 deletions
|
@ -9,8 +9,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
// versionCode and versionName must be hardcoded to support F-droid
|
||||
versionCode 702001
|
||||
versionName '7.2.0'
|
||||
versionCode 702011
|
||||
versionName '7.2.1'
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
multiDexEnabled true
|
||||
|
|
|
@ -15,4 +15,6 @@ class FixedDimensionRepository : IDimensionRepository {
|
|||
override fun displaySize(): Size = Size(50 * 15, 50 * 30)
|
||||
|
||||
override fun actionBarSize(): Int = 50
|
||||
|
||||
override fun navigationBarHeight(): Int = 0
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
// versionCode and versionName must be hardcoded to support F-droid
|
||||
versionCode 702001
|
||||
versionName '7.2.0'
|
||||
versionCode 702011
|
||||
versionName '7.2.1'
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
|
|
@ -12,6 +12,7 @@ interface IDimensionRepository {
|
|||
fun areaSeparator(): Float
|
||||
fun displaySize(): Size
|
||||
fun actionBarSize(): Int
|
||||
fun navigationBarHeight(): Int
|
||||
}
|
||||
|
||||
data class Size(
|
||||
|
@ -51,4 +52,17 @@ class DimensionRepository(
|
|||
styledAttributes.recycle()
|
||||
return actionBarSize
|
||||
}
|
||||
|
||||
override fun navigationBarHeight(): Int {
|
||||
// TODO use official mode if available
|
||||
val resources = context.resources
|
||||
val resourceId: Int = resources.getIdentifier(NAVIGATION_BAR_HEIGHT, DEF_TYPE_DIMEN, DEF_PACKAGE)
|
||||
return if (resourceId > 0) { resources.getDimensionPixelSize(resourceId) } else 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NAVIGATION_BAR_HEIGHT = "navigation_bar_height"
|
||||
private const val DEF_TYPE_DIMEN = "dimen"
|
||||
private const val DEF_PACKAGE = "android"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,10 +35,12 @@ class MinefieldRepository : IMinefieldRepository {
|
|||
dimensionRepository: IDimensionRepository
|
||||
): Minefield {
|
||||
val fieldSize = dimensionRepository.areaSize()
|
||||
val verticalGap = if (dimensionRepository.navigationBarHeight() > 0)
|
||||
VERTICAL_STANDARD_GAP else VERTICAL_STANDARD_GAP_WITHOUT_BOTTOM
|
||||
|
||||
val display = dimensionRepository.displaySize()
|
||||
val calculatedWidth = ((display.width / fieldSize).toInt() - HORIZONTAL_STANDARD_GAP)
|
||||
val calculatedHeight = ((display.height / fieldSize).toInt() - VERTICAL_STANDARD_GAP)
|
||||
val calculatedHeight = ((display.height / fieldSize).toInt() - verticalGap)
|
||||
val finalWidth = calculatedWidth.coerceAtLeast(MIN_STANDARD_WIDTH)
|
||||
val finalHeight = calculatedHeight.coerceAtLeast(MIN_STANDARD_HEIGHT)
|
||||
val finalMines = (finalWidth * finalHeight * CUSTOM_LEVEL_RATIO).toInt()
|
||||
|
@ -55,6 +57,7 @@ class MinefieldRepository : IMinefieldRepository {
|
|||
|
||||
private const val CUSTOM_LEVEL_RATIO = 0.2
|
||||
private const val HORIZONTAL_STANDARD_GAP = 1
|
||||
private const val VERTICAL_STANDARD_GAP_WITHOUT_BOTTOM = 4
|
||||
private const val VERTICAL_STANDARD_GAP = 3
|
||||
private const val MIN_STANDARD_WIDTH = 6
|
||||
private const val MIN_STANDARD_HEIGHT = 9
|
||||
|
|
|
@ -14,10 +14,13 @@ import dev.lucasnlm.antimine.common.R
|
|||
import dev.lucasnlm.antimine.common.level.models.Area
|
||||
import dev.lucasnlm.antimine.common.level.models.AreaPaintSettings
|
||||
import dev.lucasnlm.antimine.common.level.viewmodel.GameViewModel
|
||||
import dev.lucasnlm.antimine.core.control.ControlStyle
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
|
||||
class AreaAdapter(
|
||||
context: Context,
|
||||
private val viewModel: GameViewModel
|
||||
private val viewModel: GameViewModel,
|
||||
private val preferencesRepository: IPreferencesRepository
|
||||
) : RecyclerView.Adapter<AreaViewHolder>() {
|
||||
|
||||
private var field = listOf<Area>()
|
||||
|
@ -74,7 +77,18 @@ class AreaAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean = false
|
||||
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
|
||||
if (preferencesRepository.controlStyle() == ControlStyle.DoubleClick) {
|
||||
val position = adapterPosition
|
||||
if (position == RecyclerView.NO_POSITION) {
|
||||
Log.d(TAG, "Item no longer exists.")
|
||||
} else if (clickEnabled) {
|
||||
viewModel.onSingleClick(position)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
itemView.setOnLongClickListener { target ->
|
||||
|
@ -91,6 +105,7 @@ class AreaAdapter(
|
|||
}
|
||||
|
||||
itemView.setOnClickListener {
|
||||
if (preferencesRepository.controlStyle() != ControlStyle.DoubleClick) {
|
||||
val position = adapterPosition
|
||||
if (position == RecyclerView.NO_POSITION) {
|
||||
Log.d(TAG, "Item no longer exists.")
|
||||
|
@ -98,6 +113,7 @@ class AreaAdapter(
|
|||
viewModel.onSingleClick(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
itemView.setOnKeyListener { _, keyCode, keyEvent ->
|
||||
var handled = false
|
||||
|
|
|
@ -3,7 +3,6 @@ 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
|
||||
|
@ -14,14 +13,17 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import dev.lucasnlm.antimine.common.level.repository.IDimensionRepository
|
||||
import dev.lucasnlm.antimine.common.level.viewmodel.GameViewModel
|
||||
import dev.lucasnlm.antimine.common.level.widget.FixedGridLayoutManager
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class CommonLevelFragment : Fragment() {
|
||||
@Inject
|
||||
lateinit var dimensionRepository: IDimensionRepository
|
||||
@Inject
|
||||
lateinit var preferencesRepository: IPreferencesRepository
|
||||
|
||||
protected val viewModel: GameViewModel by activityViewModels()
|
||||
protected val areaAdapter by lazy { AreaAdapter(requireContext(), viewModel) }
|
||||
protected val areaAdapter by lazy { AreaAdapter(requireContext(), viewModel, preferencesRepository) }
|
||||
protected lateinit var recyclerGrid: RecyclerView
|
||||
|
||||
abstract val levelFragmentResId: Int
|
||||
|
@ -54,19 +56,9 @@ abstract class CommonLevelFragment : Fragment() {
|
|||
val displayMetrics = DisplayMetrics()
|
||||
windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
|
||||
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 bottom = if (resourceId > 0) { resources.getDimensionPixelSize(resourceId) } else 0
|
||||
|
||||
val height = displayMetrics.heightPixels - actionBarHeight - bottom
|
||||
val height = recyclerGrid.measuredHeight
|
||||
val recyclerViewHeight = (dimensionRepository.areaSize() * boardHeight)
|
||||
val separatorsHeight = (dimensionRepository.areaSeparator() * (boardHeight - 1))
|
||||
val separatorsHeight = (2 * dimensionRepository.areaSeparator() * (boardHeight - 1))
|
||||
|
||||
return ((height - recyclerViewHeight - separatorsHeight) / 2).coerceAtLeast(0.0f).toInt()
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<item name="colorPrimary">@color/primary</item>
|
||||
<item name="colorPrimaryDark">@color/primary_dark</item>
|
||||
<item name="colorAccent">@color/accent</item>
|
||||
<item name="android:background">@color/primary</item>
|
||||
<item name="android:windowTranslucentNavigation">true</item>
|
||||
</style>
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<item name="colorPrimary">@color/primary</item>
|
||||
<item name="colorPrimaryDark">@color/primary_dark</item>
|
||||
<item name="colorAccent">@color/accent</item>
|
||||
<item name="android:background">@color/primary</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Splash" parent="Theme.AppCompat.NoActionBar">
|
||||
|
|
|
@ -15,4 +15,6 @@ class FixedDimensionRepository : IDimensionRepository {
|
|||
override fun displaySize(): Size = Size(50 * 15, 50 * 30)
|
||||
|
||||
override fun actionBarSize(): Int = 50
|
||||
|
||||
override fun navigationBarHeight(): Int = 0
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package dev.lucasnlm.antimine.common.level.repository
|
||||
|
||||
import dev.lucasnlm.antimine.common.level.models.Difficulty
|
||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
import dev.lucasnlm.antimine.core.preferences.IPreferencesRepository
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class MinefieldRepositoryTest {
|
||||
private val beginnerMinefield = Minefield(9, 9, 10)
|
||||
private val intermediateMinefield = Minefield(16, 16, 40)
|
||||
private val expertMinefield = Minefield(24, 24, 99)
|
||||
|
||||
@Test
|
||||
fun testStandardSizeCalcWithoutNavigationBar() {
|
||||
val minefieldRepository = MinefieldRepository()
|
||||
val preferencesRepository = mockk<IPreferencesRepository>(relaxed = true)
|
||||
val dimensionRepository = mockk<IDimensionRepository>(relaxed = true) {
|
||||
every { areaSize() } returns 10.0f
|
||||
every { navigationBarHeight() } returns 0
|
||||
every { displaySize() } returns Size(1000, 1000)
|
||||
}
|
||||
|
||||
val minefield = minefieldRepository.fromDifficulty(
|
||||
Difficulty.Standard, dimensionRepository, preferencesRepository
|
||||
)
|
||||
|
||||
assertEquals(Minefield(99, 96, 1900), minefield)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStandardSizeCalcWithNavigationBar() {
|
||||
val minefieldRepository = MinefieldRepository()
|
||||
val preferencesRepository = mockk<IPreferencesRepository>(relaxed = true)
|
||||
val dimensionRepository = mockk<IDimensionRepository>(relaxed = true) {
|
||||
every { areaSize() } returns 10.0f
|
||||
every { navigationBarHeight() } returns 100
|
||||
every { displaySize() } returns Size(1000, 1000)
|
||||
}
|
||||
|
||||
val minefield = minefieldRepository.fromDifficulty(
|
||||
Difficulty.Standard, dimensionRepository, preferencesRepository
|
||||
)
|
||||
|
||||
assertEquals(Minefield(99, 97, 1920), minefield)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBeginnerMinefield() {
|
||||
val minefieldRepository = MinefieldRepository()
|
||||
val preferencesRepository = mockk<IPreferencesRepository>(relaxed = true)
|
||||
val dimensionRepository = mockk<IDimensionRepository>(relaxed = true)
|
||||
|
||||
val minefield = minefieldRepository.fromDifficulty(
|
||||
Difficulty.Beginner, dimensionRepository, preferencesRepository
|
||||
)
|
||||
assertEquals(beginnerMinefield, minefield)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIntermediateMinefield() {
|
||||
val minefieldRepository = MinefieldRepository()
|
||||
val preferencesRepository = mockk<IPreferencesRepository>(relaxed = true)
|
||||
val dimensionRepository = mockk<IDimensionRepository>(relaxed = true)
|
||||
|
||||
val minefield = minefieldRepository.fromDifficulty(
|
||||
Difficulty.Intermediate, dimensionRepository, preferencesRepository
|
||||
)
|
||||
assertEquals(intermediateMinefield, minefield)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExpertMinefieldMinefield() {
|
||||
val minefieldRepository = MinefieldRepository()
|
||||
val preferencesRepository = mockk<IPreferencesRepository>(relaxed = true)
|
||||
val dimensionRepository = mockk<IDimensionRepository>(relaxed = true)
|
||||
|
||||
val minefield = minefieldRepository.fromDifficulty(
|
||||
Difficulty.Expert, dimensionRepository, preferencesRepository
|
||||
)
|
||||
assertEquals(expertMinefield, minefield)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCustomMinefieldMinefield() {
|
||||
val minefieldRepository = MinefieldRepository()
|
||||
val preferencesRepository = mockk<IPreferencesRepository>(relaxed = true) {
|
||||
every { customGameMode() } returns Minefield(25, 20, 12)
|
||||
}
|
||||
val dimensionRepository = mockk<IDimensionRepository>(relaxed = true)
|
||||
|
||||
val minefield = minefieldRepository.fromDifficulty(
|
||||
Difficulty.Custom, dimensionRepository, preferencesRepository
|
||||
)
|
||||
assertEquals(Minefield(25, 20, 12), minefield)
|
||||
}
|
||||
}
|
|
@ -6,8 +6,8 @@ android {
|
|||
compileSdkVersion 29
|
||||
|
||||
defaultConfig {
|
||||
versionCode 702001 // MMmmPPv
|
||||
versionName '7.2.0'
|
||||
versionCode 702011 // MMmmPPv
|
||||
versionName '7.2.1'
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
resConfigs 'ar', 'en', 'cs', 'de', 'el', 'es', 'fr', 'ja', 'pt', 'ru', 'tr', 'uk', 'vi', 'zh'
|
||||
|
|
|
@ -6,8 +6,8 @@ android {
|
|||
compileSdkVersion 29
|
||||
|
||||
defaultConfig {
|
||||
versionCode 702001 // MMmmPPv
|
||||
versionName '7.2.0'
|
||||
versionCode 702011 // MMmmPPv
|
||||
versionName '7.2.1'
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 29
|
||||
resConfigs 'ar', 'en', 'cs', 'de', 'el', 'es', 'fr', 'ja', 'pt', 'ru', 'tr', 'uk', 'vi', 'zh'
|
||||
|
|
|
@ -9,8 +9,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
// versionCode and versionName must be hardcoded to support F-droid
|
||||
versionCode 702001
|
||||
versionName '7.2.0'
|
||||
versionCode 702011
|
||||
versionName '7.2.1'
|
||||
applicationId 'dev.lucasnlm.antimine'
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 29
|
||||
|
|
Loading…
Reference in a new issue