feat: make boards drawer width adaptive by screen class
This commit is contained in:
@@ -149,7 +149,7 @@ Kanbn4Droid is an unofficial app to connect to and manipulate data stored in sel
|
||||
|
||||
**Settings view**
|
||||
- The view is available from a side panel that can be shown in the Boards list view by pulling in from the left side of the screen.
|
||||
- The side panel only occupies up to a third of the screen.
|
||||
- On smaller screens the side panel occupies full width; on larger screens it occupies up to two-thirds of the screen, capped by a max width.
|
||||
- The view behind the side panel dims when the side panel is open.
|
||||
- The side panel shows the following content from top to bottom:
|
||||
- Username as a title in bold text of the currently active user obtained with the "users/me" endpoint of the Kan.bn API.
|
||||
@@ -169,7 +169,7 @@ Kanbn4Droid is an unofficial app to connect to and manipulate data stored in sel
|
||||
- All settings are managed using the AndroidX Preferences library.
|
||||
- Changing any settings makes it apply instantly when leaving the settings view without logging out.
|
||||
- Current status: implemented through a left-side drawer in `BoardsActivity` plus an in-place settings dialog (`SettingsDialogFragment` + `SettingsPreferencesFragment`) using AndroidX Preferences.
|
||||
- Drawer behavior is implemented with `DrawerLayout`: left-edge gesture + toolbar open action, dimmed background, and runtime width capped to one-third of screen width.
|
||||
- Drawer behavior is implemented with `DrawerLayout`: left-edge gesture + toolbar open action, dimmed background, and adaptive runtime width (small screens: full width; large screens: min(two-thirds of window, max width cap)).
|
||||
- Drawer content is implemented with profile header (`users/me`), workspaces list with active highlight, settings entry, retry/error states, and logout action with confirmation.
|
||||
- Workspace switching is implemented with active-workspace persistence and boards refresh; switch failures restore previous selection and unauthorized responses force sign-out.
|
||||
- Settings dialog implements Theme/Base URL/API key drafts, save-and-close apply flow, immediate theme application, credential re-auth on URL/key changes, and safe rollback on apply failure.
|
||||
|
||||
@@ -281,7 +281,7 @@ class BoardsFlowTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun drawerWidthNeverExceedsOneThirdOfScreen() {
|
||||
fun drawerWidthIsAdaptiveForSmallAndLargeScreens() {
|
||||
MainActivity.dependencies.apiClientFactory = {
|
||||
FakeBoardsApiClient(
|
||||
boards = mutableListOf(BoardSummary("1", "Alpha")),
|
||||
@@ -292,9 +292,19 @@ class BoardsFlowTest {
|
||||
val scenario = ActivityScenario.launch(BoardsActivity::class.java)
|
||||
|
||||
scenario.onActivity { activity ->
|
||||
val drawerLayout = activity.findViewById<DrawerLayout>(R.id.boardsDrawerLayout)
|
||||
val drawerContent = activity.findViewById<View>(R.id.boardsDrawerContent)
|
||||
val displayWidthPx = activity.resources.displayMetrics.widthPixels
|
||||
assertTrue(drawerContent.layoutParams.width <= displayWidthPx / 3)
|
||||
val resources = activity.resources
|
||||
val breakpointSwDp = resources.getInteger(R.integer.boards_drawer_tablet_breakpoint_sw_dp)
|
||||
val maxWidthPx = resources.getDimensionPixelSize(R.dimen.boards_drawer_max_width)
|
||||
val isSmallScreen = resources.configuration.smallestScreenWidthDp < breakpointSwDp
|
||||
val expectedWidth = if (isSmallScreen) {
|
||||
drawerLayout.width
|
||||
} else {
|
||||
minOf((drawerLayout.width * 2) / 3, maxWidthPx)
|
||||
}
|
||||
|
||||
assertEquals(expectedWidth, drawerContent.layoutParams.width)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.doOnLayout
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
@@ -40,6 +41,7 @@ import space.hackenslacker.kanbn4droid.app.boards.BoardsRepository
|
||||
import space.hackenslacker.kanbn4droid.app.boards.BoardsUiEvent
|
||||
import space.hackenslacker.kanbn4droid.app.boards.BoardsUiState
|
||||
import space.hackenslacker.kanbn4droid.app.boards.BoardsViewModel
|
||||
import space.hackenslacker.kanbn4droid.app.boards.BoardsDrawerWidthCalculator
|
||||
import space.hackenslacker.kanbn4droid.app.boards.DrawerDataErrorCode
|
||||
import space.hackenslacker.kanbn4droid.app.boarddetail.BoardDetailActivity
|
||||
import space.hackenslacker.kanbn4droid.app.settings.SettingsDialogFragment
|
||||
@@ -284,13 +286,20 @@ class BoardsActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun applyDrawerWidth() {
|
||||
drawerLayout.doOnLayout {
|
||||
val maxWidthPx = resources.getDimensionPixelSize(R.dimen.boards_drawer_max_width)
|
||||
val displayWidthPx = resources.displayMetrics.widthPixels
|
||||
val computedWidth = minOf(displayWidthPx / 3, maxWidthPx)
|
||||
val breakpointSwDp = resources.getInteger(R.integer.boards_drawer_tablet_breakpoint_sw_dp)
|
||||
val computedWidth = BoardsDrawerWidthCalculator.computeWidth(
|
||||
smallestScreenWidthDp = resources.configuration.smallestScreenWidthDp,
|
||||
tabletBreakpointSwDp = breakpointSwDp,
|
||||
windowWidthPx = drawerLayout.width,
|
||||
maxWidthPx = maxWidthPx,
|
||||
)
|
||||
drawerContent.layoutParams = drawerContent.layoutParams.apply {
|
||||
width = computedWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun forceSignOutToLogin() {
|
||||
lifecycleScope.launch {
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package space.hackenslacker.kanbn4droid.app.boards
|
||||
|
||||
object BoardsDrawerWidthCalculator {
|
||||
fun computeWidth(
|
||||
smallestScreenWidthDp: Int,
|
||||
tabletBreakpointSwDp: Int,
|
||||
windowWidthPx: Int,
|
||||
maxWidthPx: Int,
|
||||
): Int {
|
||||
if (smallestScreenWidthDp < tabletBreakpointSwDp) {
|
||||
return windowWidthPx
|
||||
}
|
||||
return minOf((windowWidthPx * 2) / 3, maxWidthPx)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="boards_drawer_tablet_breakpoint_sw_dp">600</integer>
|
||||
</resources>
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package space.hackenslacker.kanbn4droid.app.boards
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class BoardsDrawerWidthCalculatorTest {
|
||||
@Test
|
||||
fun smallScreenUsesFullWidth() {
|
||||
val result = BoardsDrawerWidthCalculator.computeWidth(
|
||||
smallestScreenWidthDp = 411,
|
||||
tabletBreakpointSwDp = 600,
|
||||
windowWidthPx = 1080,
|
||||
maxWidthPx = 900,
|
||||
)
|
||||
|
||||
assertEquals(1080, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun smallScreenUsesFullWidthIgnoringCap() {
|
||||
val result = BoardsDrawerWidthCalculator.computeWidth(
|
||||
smallestScreenWidthDp = 411,
|
||||
tabletBreakpointSwDp = 600,
|
||||
windowWidthPx = 1080,
|
||||
maxWidthPx = 320,
|
||||
)
|
||||
|
||||
assertEquals(1080, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun boundaryAtBreakpointUsesLargeRule() {
|
||||
val result = BoardsDrawerWidthCalculator.computeWidth(
|
||||
smallestScreenWidthDp = 600,
|
||||
tabletBreakpointSwDp = 600,
|
||||
windowWidthPx = 1000,
|
||||
maxWidthPx = 700,
|
||||
)
|
||||
|
||||
assertEquals(666, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun largeScreenAppliesTwoThirdsWithCap() {
|
||||
val result = BoardsDrawerWidthCalculator.computeWidth(
|
||||
smallestScreenWidthDp = 1024,
|
||||
tabletBreakpointSwDp = 600,
|
||||
windowWidthPx = 1024,
|
||||
maxWidthPx = 700,
|
||||
)
|
||||
|
||||
assertEquals(682, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun boundaryBelowBreakpointUsesSmallRule() {
|
||||
val result = BoardsDrawerWidthCalculator.computeWidth(
|
||||
smallestScreenWidthDp = 599,
|
||||
tabletBreakpointSwDp = 600,
|
||||
windowWidthPx = 1000,
|
||||
maxWidthPx = 700,
|
||||
)
|
||||
|
||||
assertEquals(1000, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun largeScreenUsesCapWhenTwoThirdsExceedsCap() {
|
||||
val result = BoardsDrawerWidthCalculator.computeWidth(
|
||||
smallestScreenWidthDp = 840,
|
||||
tabletBreakpointSwDp = 600,
|
||||
windowWidthPx = 840,
|
||||
maxWidthPx = 560,
|
||||
)
|
||||
|
||||
assertEquals(560, result)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user