Skip to content

Commit

Permalink
Implement new design and support for landscape screens (#313)
Browse files Browse the repository at this point in the history
* Make monster compendium flexible for large screens

* Implement new design

* Implement new design for monster card

* Implement new design for monster detail part 1

* Implement new design for monster form image

* Add support for landscape screens

* Fix GetMonsterPreviewsBySectionUseCaseTest

* Implement new design for monster content manager

* Implement new design for folder preview monster icon

* Adjust bottom sheet for large screens

* Adjust settings and monster detail bottom sheets

* Make compendium more horizontal

* Add monster card elevation

* Fix app crashing when clicking on monster folder preview

* Change monster detail background

* Apply new design for monster folders

* Adjust monster content padding
  • Loading branch information
alexandregpereira authored Aug 13, 2024
1 parent 5256ca9 commit 1c68a4f
Show file tree
Hide file tree
Showing 103 changed files with 1,392 additions and 1,558 deletions.
4 changes: 0 additions & 4 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ multiplatform {
implementation(project(":domain:app:core"))
implementation(project(":domain:sync:core"))

implementation(project(":feature:folder-detail:event"))
implementation(project(":feature:folder-list:event"))
implementation(project(":feature:folder-preview:event"))
implementation(project(":feature:monster-content-manager:event"))
implementation(project(":feature:share-content:event"))
implementation(project(":domain:monster:event"))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package br.alexandregpereira.hunter.app

import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import br.alexandregpereira.hunter.ui.util.createComposeView

Expand All @@ -29,10 +28,5 @@ class MainActivity : AppCompatActivity() {
HunterApp(contentPadding = contentPadding)
}
setContentView(view)
view.apply {
systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
}
}
}
4 changes: 2 additions & 2 deletions app/src/androidMain/res/values-night/themes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
<item name="colorSecondaryVariant">@color/gray_scale_900</item>

<item name="android:colorBackground">@color/black</item>
<item name="android:statusBarColor">@color/gray_scale_900_alpha_060</item>
<item name="android:navigationBarColor">@color/black_alpha_020</item>
<item name="android:statusBarColor">@color/black</item>
<item name="android:navigationBarColor">@color/black</item>

<!--System/Platform attributes-->
<item name="android:windowLightStatusBar" tools:ignore="NewApi">false</item>
Expand Down
4 changes: 2 additions & 2 deletions app/src/androidMain/res/values/themes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
<item name="colorSecondaryVariant">@color/white</item>

<item name="android:colorBackground">@color/gray_scale_100</item>
<item name="android:statusBarColor">@color/white_alpha_060</item>
<item name="android:navigationBarColor">@color/white_alpha_020</item>
<item name="android:statusBarColor">@color/gray_scale_100</item>
<item name="android:navigationBarColor">@color/gray_scale_100</item>

<!--Styles-->
<item name="navigationViewStyle">@style/Widget.Hunter.NavigationView</item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,20 @@ package br.alexandregpereira.hunter.app

import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.unit.dp
import br.alexandregpereira.hunter.app.di.AppStateRecoveryQualifier
import br.alexandregpereira.hunter.app.ui.AppMainScreen
import br.alexandregpereira.hunter.app.ui.AppMainLandscapeScreen
import br.alexandregpereira.hunter.app.ui.AppMainPortraitScreen
import br.alexandregpereira.hunter.ui.compose.AppWindow
import br.alexandregpereira.hunter.ui.compose.LocalScreenSize
import br.alexandregpereira.hunter.ui.compose.ScreenSizeType.LandscapeCompact
import br.alexandregpereira.hunter.ui.compose.ScreenSizeType.LandscapeExpanded
import br.alexandregpereira.hunter.ui.compose.ScreenSizeType.Portrait
import br.alexandregpereira.hunter.ui.compose.StateRecoveryLaunchedEffect
import br.alexandregpereira.hunter.ui.compose.getPlatformScreenSizeInfo
import org.koin.compose.KoinContext
import org.koin.compose.koinInject
import org.koin.core.qualifier.named
Expand All @@ -41,10 +48,30 @@ internal fun HunterApp(

val viewModel: MainViewModel = koinInject()
val state by viewModel.state.collectAsState()
AppMainScreen(
state = state,
contentPadding = contentPadding,
onEvent = viewModel::onEvent
)
CompositionLocalProvider(
LocalScreenSize provides getPlatformScreenSizeInfo()
) {
val screenSize = LocalScreenSize.current

when (screenSize.type) {
Portrait -> AppMainPortraitScreen(
state = state,
contentPadding = contentPadding,
onEvent = viewModel::onEvent
)
LandscapeCompact,
LandscapeExpanded -> {
val leftPanelFraction = if (screenSize.type == LandscapeExpanded) {
0.7f
} else 0.5f
AppMainLandscapeScreen(
state = state,
contentPadding = contentPadding,
leftPanelFraction = leftPanelFraction,
onEvent = viewModel::onEvent
)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,20 @@ import br.alexandregpereira.hunter.app.BottomBarItemIcon.SEARCH
import br.alexandregpereira.hunter.app.BottomBarItemIcon.SETTINGS
import br.alexandregpereira.hunter.app.MainViewEvent.BottomNavigationItemClick
import br.alexandregpereira.hunter.app.event.AppEventDispatcher
import br.alexandregpereira.hunter.event.folder.detail.FolderDetailResultListener
import br.alexandregpereira.hunter.event.folder.detail.collectOnVisibilityChanges
import br.alexandregpereira.hunter.event.folder.list.FolderListResultListener
import br.alexandregpereira.hunter.event.folder.list.collectOnItemSelectionVisibilityChanges
import br.alexandregpereira.hunter.event.systembar.BottomBarEvent.AddTopContent
import br.alexandregpereira.hunter.event.systembar.BottomBarEvent.RemoveTopContent
import br.alexandregpereira.hunter.event.systembar.BottomBarEventManager
import br.alexandregpereira.hunter.event.systembar.dispatchAddTopContentEvent
import br.alexandregpereira.hunter.event.systembar.dispatchRemoveTopContentEvent
import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewEvent
import br.alexandregpereira.hunter.folder.preview.event.FolderPreviewEventDispatcher
import br.alexandregpereira.hunter.localization.AppReactiveLocalization
import br.alexandregpereira.hunter.monster.content.event.MonsterContentManagerEventListener
import br.alexandregpereira.hunter.monster.content.event.collectOnVisibilityChanges
import br.alexandregpereira.hunter.monster.event.MonsterEvent.OnVisibilityChanges.Hide
import br.alexandregpereira.hunter.monster.event.MonsterEvent.OnVisibilityChanges.Show
import br.alexandregpereira.hunter.monster.event.MonsterEventDispatcher
import br.alexandregpereira.hunter.monster.event.collectOnVisibilityChanges
import br.alexandregpereira.hunter.state.UiModel
import br.alexandregpereira.hunter.ui.StateRecovery
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

internal class MainViewModel(
private val monsterDetailEventListener: MonsterEventDispatcher,
private val folderDetailResultListener: FolderDetailResultListener,
private val folderListResultListener: FolderListResultListener,
private val monsterContentManagerEventListener: MonsterContentManagerEventListener,
private val folderPreviewEventDispatcher: FolderPreviewEventDispatcher,
private val bottomBarEventManager: BottomBarEventManager,
private val appLocalization: AppReactiveLocalization,
private val stateRecovery: StateRecovery,
appEventDispatcher: AppEventDispatcher,
) : UiModel<MainViewState>(MainViewState()) {

init {
setState { updateState(stateRecovery) }
observeBottomBarEvents()
observeMonsterDetailEvents()
observeFolderDetailResults()
observeFolderListResults()
observeMonsterContentManagerEvents()
observeLanguageChanges()
appEventDispatcher.observeEvents()
}
Expand Down Expand Up @@ -82,106 +54,27 @@ internal class MainViewModel(
}.launchIn(scope)
}

private fun observeBottomBarEvents() {
bottomBarEventManager.events.onEach { event ->
when (event) {
is AddTopContent -> setStateAndSave { addTopContentStack(event.topContentId) }
is RemoveTopContent -> setStateAndSave { removeTopContentStack(event.topContentId) }
}
}.launchIn(scope)
}

private fun observeMonsterDetailEvents() {
monsterDetailEventListener.collectOnVisibilityChanges { event ->
when (event) {
is Show -> {
dispatchAddTopContentEvent(TopContent.MONSTER_DETAIL.name)
dispatchFolderPreviewEvent(show = false)
}
Hide -> {
dispatchRemoveTopContentEvent(TopContent.MONSTER_DETAIL.name)
dispatchFolderPreviewEvent(show = true)
}
}
}.launchIn(scope)
}

private fun observeMonsterContentManagerEvents() {
monsterContentManagerEventListener.collectOnVisibilityChanges { isShowing ->
dispatchTopContentEvent(TopContent.MONSTER_CONTENT_MANAGER.name, isShowing)
}.launchIn(scope)
}

private fun observeFolderDetailResults() {
folderDetailResultListener.collectOnVisibilityChanges { result ->
dispatchTopContentEvent(TopContent.FOLDER_DETAIL.name, result.isShowing)
}.launchIn(scope)
}

private fun observeFolderListResults() {
folderListResultListener.collectOnItemSelectionVisibilityChanges { result ->
dispatchTopContentEvent(TopContent.FOLDER_LIST_SELECTION.name, result.isShowing)
dispatchFolderPreviewEvent(result.isShowing.not())
}.launchIn(scope)
}

fun onEvent(event: MainViewEvent) {
when (event) {
is BottomNavigationItemClick -> {
setStateAndSave { copy(bottomBarItemSelectedIndex = bottomBarItems.indexOf(event.item)) }
dispatchFolderPreviewEvent(show = event.item.icon != SETTINGS)
}
}
}

private fun dispatchFolderPreviewEvent(show: Boolean) {
val folderPreviewEvent = if (show) {
FolderPreviewEvent.ShowFolderPreview
} else {
FolderPreviewEvent.HideFolderPreview
}
folderPreviewEventDispatcher.dispatchEvent(folderPreviewEvent)
}

private fun dispatchAddTopContentEvent(topContentId: String) {
bottomBarEventManager.dispatchAddTopContentEvent(topContentId)
}

private fun dispatchRemoveTopContentEvent(topContentId: String) {
bottomBarEventManager.dispatchRemoveTopContentEvent(topContentId)
}

private fun dispatchTopContentEvent(topContentId: String, show: Boolean) {
if (show) {
dispatchAddTopContentEvent(topContentId)
} else {
dispatchRemoveTopContentEvent(topContentId)
}
}

private fun setStateAndSave(block: MainViewState.() -> MainViewState) {
setState { block().saveState(stateRecovery) }
}

private fun MainViewState.saveState(stateRecovery: StateRecovery): MainViewState {
stateRecovery["app:bottomBarItemSelectedIndex"] = bottomBarItemSelectedIndex
stateRecovery["app:topContentStack"] = topContentStack
stateRecovery.dispatchChanges()
return this
}

private fun MainViewState.updateState(bundle: Map<String, Any?>): MainViewState {
return copy(
bottomBarItemSelectedIndex = bundle["app:bottomBarItemSelectedIndex"] as? Int ?: 0,
topContentStack = (bundle["app:topContentStack"] as? Set<*>)
?.map { it as String }?.toSet() ?: emptySet(),
)
}
}

private enum class TopContent {
MONSTER_DETAIL,
FOLDER_DETAIL,
MONSTER_CONTENT_MANAGER,
FOLDER_LIST_SELECTION,
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,11 @@ import org.jetbrains.compose.resources.DrawableResource
internal data class MainViewState(
val bottomBarItemSelectedIndex: Int = 0,
val bottomBarItems: List<BottomBarItem> = emptyList(),
internal val topContentStack: Set<String> = setOf(),
) {

val showBottomBar: Boolean = topContentStack.isEmpty()
val bottomBarItemSelected: BottomBarItem? = bottomBarItems.getOrNull(bottomBarItemSelectedIndex)
}

internal fun MainViewState.addTopContentStack(
topContent: String,
): MainViewState {
val topContentStack = topContentStack + topContent
return copy(topContentStack = topContentStack)
}

internal fun MainViewState.removeTopContentStack(
topContent: String,
): MainViewState {
val topContentStack = topContentStack.toMutableSet().apply {
remove(topContent)
}.toSet()
return copy(
topContentStack = topContentStack,
)
}

internal enum class BottomBarItemIcon(val value: DrawableResource) {
COMPENDIUM(value = Res.drawable.ic_book),
SEARCH(value = Res.drawable.ic_search),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import br.alexandregpereira.hunter.app.event.appEventModule
import br.alexandregpereira.hunter.data.di.dataModules
import br.alexandregpereira.hunter.detail.di.featureMonsterDetailModule
import br.alexandregpereira.hunter.domain.di.domainModules
import br.alexandregpereira.hunter.event.systembar.bottomBarEventModule
import br.alexandregpereira.hunter.folder.detail.di.featureFolderDetailModule
import br.alexandregpereira.hunter.folder.insert.di.featureFolderInsertModule
import br.alexandregpereira.hunter.folder.list.di.featureFolderListModule
Expand Down Expand Up @@ -55,7 +54,6 @@ internal fun KoinApplication.initKoinModules() {
)
modules(
analyticsModule,
bottomBarEventModule,
localizationModule,
monsterEventModule,
appEventModule,
Expand All @@ -71,12 +69,6 @@ private val appModule = module {

single {
MainViewModel(
monsterDetailEventListener = get(),
folderDetailResultListener = get(),
folderListResultListener = get(),
monsterContentManagerEventListener = get(),
folderPreviewEventDispatcher = get(),
bottomBarEventManager = get(),
appLocalization = get(),
stateRecovery = get(named(AppStateRecoveryQualifier)),
appEventDispatcher = get(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.height
Expand All @@ -36,7 +34,6 @@ import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.LocalContentColor
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ProvideTextStyle
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.runtime.Composable
Expand All @@ -60,31 +57,28 @@ import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import br.alexandregpereira.hunter.app.BottomBarItem
import br.alexandregpereira.hunter.ui.theme.HunterTheme
import br.alexandregpereira.hunter.ui.compose.Window
import br.alexandregpereira.hunter.ui.util.BottomNavigationHeight
import org.jetbrains.compose.resources.DrawableResource
import org.jetbrains.compose.resources.painterResource
import kotlin.math.roundToInt

@Composable
internal fun BoxScope.AppBottomNavigation(
internal fun AppBottomNavigation(
showBottomBar: Boolean,
bottomBarItemSelectedIndex: Int,
bottomBarItems: List<BottomBarItem>,
contentPadding: PaddingValues = PaddingValues(),
modifier: Modifier = Modifier,
onClick: (BottomBarItem) -> Unit = {}
) = HunterTheme {
) {
AnimatedVisibility(
visible = showBottomBar,
enter = slideInVertically { fullHeight -> fullHeight },
exit = slideOutVertically { fullHeight -> fullHeight },
modifier = Modifier.align(Alignment.BottomStart)
modifier = modifier,
) {
Surface(
modifier = Modifier
.background(MaterialTheme.colors.surface),
elevation = 4.dp
) {
Window {
val paddingBottom = contentPadding.calculateBottomPadding()
BottomNavigation(
backgroundColor = MaterialTheme.colors.surface,
Expand Down
Loading

0 comments on commit 1c68a4f

Please sign in to comment.