@@ -9,21 +9,102 @@ import com.itsaky.androidide.R
99import com.itsaky.androidide.databinding.ContentEditorBinding
1010import kotlin.math.abs
1111
12- class FullscreenController (
12+ class FullscreenManager (
1313 private val contentBinding : ContentEditorBinding ,
1414 private val bottomSheetBehavior : BottomSheetBehavior <out View ?>,
1515 private val closeDrawerAction : () -> Unit ,
1616 private val onFullscreenToggleRequested : () -> Unit ,
1717) {
18+ private sealed interface FullscreenUiState {
19+ val isFullscreen: Boolean
20+
21+ data object Fullscreen : FullscreenUiState {
22+ override val isFullscreen = true
23+ }
24+
25+ data object Windowed : FullscreenUiState {
26+ override val isFullscreen = false
27+ }
28+
29+ companion object {
30+ fun from (isFullscreen : Boolean ): FullscreenUiState {
31+ return if (isFullscreen) Fullscreen else Windowed
32+ }
33+ }
34+ }
35+
36+ private sealed interface FullscreenRenderCommand {
37+ val targetState: FullscreenUiState
38+ val animate: Boolean
39+
40+ fun apply (manager : FullscreenManager )
41+
42+ data class EnterFullscreen (
43+ override val animate : Boolean ,
44+ ) : FullscreenRenderCommand {
45+ override val targetState = FullscreenUiState .Fullscreen
46+
47+ override fun apply (manager : FullscreenManager ) {
48+ manager.applyFullscreen(animate)
49+ }
50+ }
51+
52+ data class ExitFullscreen (
53+ override val animate : Boolean ,
54+ ) : FullscreenRenderCommand {
55+ override val targetState = FullscreenUiState .Windowed
56+
57+ override fun apply (manager : FullscreenManager ) {
58+ manager.applyNonFullscreen(animate)
59+ }
60+ }
61+
62+ data class Refresh (
63+ override val targetState : FullscreenUiState ,
64+ ) : FullscreenRenderCommand {
65+ override val animate = false
66+
67+ override fun apply (manager : FullscreenManager ) {
68+ if (targetState.isFullscreen) {
69+ manager.applyFullscreen(animate = false )
70+ } else {
71+ manager.applyNonFullscreen(animate = false )
72+ }
73+ }
74+ }
75+
76+ companion object {
77+ fun resolve (
78+ currentState : FullscreenUiState ,
79+ targetState : FullscreenUiState ,
80+ animate : Boolean ,
81+ ): FullscreenRenderCommand {
82+ val shouldAnimate = animate && currentState != targetState
83+
84+ if (! shouldAnimate) {
85+ return Refresh (targetState)
86+ }
87+
88+ return if (targetState.isFullscreen) {
89+ EnterFullscreen (animate = true )
90+ } else {
91+ ExitFullscreen (animate = true )
92+ }
93+ }
94+ }
95+ }
96+
1897 private val topBar = contentBinding.editorAppBarLayout
1998 private val appBarContent = contentBinding.editorAppbarContent
2099 private val editorContainer = contentBinding.editorContainer
21100 private val fullscreenToggle = contentBinding.btnFullscreenToggle
22101
23102 private var isBound = false
24103 private var isTransitioning = false
25- private var currentFullscreen = false
104+ private var currentState : FullscreenUiState = FullscreenUiState . Windowed
26105 private var defaultSkipCollapsed = false
106+ private var transitionToken = 0L
107+ private var pendingTransitionToken = 0L
27108
28109 private val transitionDurationMs = 350L
29110
@@ -68,28 +149,22 @@ class FullscreenController(
68149 bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallback)
69150 bottomSheetBehavior.skipCollapsed = defaultSkipCollapsed
70151 fullscreenToggle.removeCallbacks(clearTransitioningRunnable)
152+ isTransitioning = false
71153 }
72154
73155 fun render (isFullscreen : Boolean , animate : Boolean ) {
74- val stateChanged = currentFullscreen != isFullscreen
75- val shouldAnimate = animate && stateChanged
76- currentFullscreen = isFullscreen
77- isTransitioning = shouldAnimate
78-
79- if (isFullscreen) {
80- applyFullscreen(shouldAnimate)
81- } else {
82- applyNonFullscreen(shouldAnimate)
83- }
84-
85- syncToggleUi(isFullscreen)
156+ val targetState = FullscreenUiState .from(isFullscreen)
157+ val command =
158+ FullscreenRenderCommand .resolve(
159+ currentState = currentState,
160+ targetState = targetState,
161+ animate = animate,
162+ )
86163
87- if (shouldAnimate) {
88- fullscreenToggle.removeCallbacks(clearTransitioningRunnable)
89- fullscreenToggle.postDelayed(clearTransitioningRunnable, transitionDurationMs)
90- } else {
91- isTransitioning = false
92- }
164+ currentState = command.targetState
165+ syncTransitionState(command)
166+ command.apply (this )
167+ syncToggleUi(command.targetState)
93168 }
94169
95170 private fun setupScrollFlags () {
@@ -101,17 +176,35 @@ class FullscreenController(
101176 }
102177
103178 private fun handleBottomSheetStateChange (newState : Int ) {
104- if (newState == BottomSheetBehavior .STATE_COLLAPSED && ! currentFullscreen) {
179+ val isCollapsedInWindowedMode =
180+ newState == BottomSheetBehavior .STATE_COLLAPSED && ! currentState.isFullscreen
181+ val isSheetRevealedWhileFullscreen =
182+ (newState == BottomSheetBehavior .STATE_EXPANDED ||
183+ newState == BottomSheetBehavior .STATE_HALF_EXPANDED ) &&
184+ currentState.isFullscreen &&
185+ ! isTransitioning
186+
187+ if (isCollapsedInWindowedMode) {
105188 bottomSheetBehavior.isHideable = false
106189 }
107190
108- if (newState == BottomSheetBehavior .STATE_EXPANDED ||
109- newState == BottomSheetBehavior .STATE_HALF_EXPANDED
110- ) {
111- if (currentFullscreen && ! isTransitioning) {
112- onFullscreenToggleRequested()
113- }
191+ if (isSheetRevealedWhileFullscreen) {
192+ onFullscreenToggleRequested()
193+ }
194+ }
195+
196+ private fun syncTransitionState (command : FullscreenRenderCommand ) {
197+ fullscreenToggle.removeCallbacks(clearTransitioningRunnable)
198+
199+ if (! command.animate) {
200+ isTransitioning = false
201+ transitionToken++
202+ return
114203 }
204+
205+ isTransitioning = true
206+ pendingTransitionToken = ++ transitionToken
207+ fullscreenToggle.postDelayed(clearTransitioningRunnable, transitionDurationMs)
115208 }
116209
117210 private fun applyFullscreen (animate : Boolean ) {
@@ -145,8 +238,8 @@ class FullscreenController(
145238 }
146239 }
147240
148- private fun syncToggleUi (isFullscreen : Boolean ) {
149- if (isFullscreen) {
241+ private fun syncToggleUi (state : FullscreenUiState ) {
242+ if (state. isFullscreen) {
150243 fullscreenToggle.setImageResource(R .drawable.ic_fullscreen_exit)
151244 fullscreenToggle.contentDescription =
152245 contentBinding.root.context.getString(R .string.desc_exit_fullscreen)
@@ -158,6 +251,8 @@ class FullscreenController(
158251 }
159252
160253 private val clearTransitioningRunnable = Runnable {
161- isTransitioning = false
254+ if (pendingTransitionToken == transitionToken) {
255+ isTransitioning = false
256+ }
162257 }
163258}
0 commit comments