Skip to content

Commit 26b4268

Browse files
authored
ADFA-3123 | Adaptive navigation patterns (#1055)
* feat(ui): implement adaptive navigation patterns for toolbars and template list * fix(ui): resolve toolbar layout ambiguity and adapt template grid spans Change toolbar to wrap_content and cap grid columns by orientation and item count. * fix: adapt toolbar in tablets and DeX mode * refactor: remove unused param * refactor: handle configuration changes with `onResume`
1 parent 99c02e8 commit 26b4268

File tree

7 files changed

+102
-205
lines changed

7 files changed

+102
-205
lines changed

app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt

Lines changed: 1 addition & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import android.view.MotionEvent
4141
import android.view.View
4242
import android.view.ViewGroup
4343
import android.view.ViewTreeObserver.OnGlobalLayoutListener
44-
import android.widget.LinearLayout
4544
import android.widget.TextView
4645
import androidx.activity.OnBackPressedCallback
4746
import androidx.activity.result.ActivityResult
@@ -603,7 +602,6 @@ abstract class BaseEditorActivity :
603602
}
604603

605604
setupToolbar()
606-
syncProjectToolbarRowForOrientation(resources.configuration.orientation)
607605
setupDrawers()
608606
content.tabs.addOnTabSelectedListener(this)
609607

@@ -641,12 +639,11 @@ abstract class BaseEditorActivity :
641639

642640
override fun onConfigurationChanged(newConfig: Configuration) {
643641
super.onConfigurationChanged(newConfig)
644-
syncProjectToolbarRowForOrientation(newConfig.orientation)
645642
}
646643

647644
private fun setupToolbar() {
648645
// Set the project name in the title TextView
649-
content.root.findViewById<android.widget.TextView>(R.id.title_text)?.apply {
646+
content.root.findViewById<TextView>(R.id.title_text)?.apply {
650647
text = editorViewModel.getProjectName()
651648
}
652649

@@ -695,90 +692,6 @@ abstract class BaseEditorActivity :
695692
}
696693
}
697694

698-
private fun syncProjectToolbarRowForOrientation(currentOrientation: Int) {
699-
val appBar = content.editorAppBarLayout
700-
val titleToolbar = content.titleToolbar
701-
val actionsToolbar = content.projectActionsToolbar
702-
703-
val titleParent = titleToolbar.parent as? ViewGroup ?: return
704-
val actionsParent = actionsToolbar.parent as? ViewGroup ?: return
705-
if (titleParent != actionsParent) return
706-
707-
val isLandscape = currentOrientation == Configuration.ORIENTATION_LANDSCAPE
708-
709-
if (isLandscape && titleParent === appBar) {
710-
val insertAt =
711-
minOf(
712-
appBar.indexOfChild(titleToolbar),
713-
appBar.indexOfChild(actionsToolbar),
714-
).coerceAtLeast(0)
715-
val row =
716-
LinearLayout(this).apply {
717-
orientation = LinearLayout.HORIZONTAL
718-
gravity = Gravity.CENTER_VERTICAL
719-
layoutParams =
720-
com.google.android.material.appbar.AppBarLayout.LayoutParams(
721-
ViewGroup.LayoutParams.MATCH_PARENT,
722-
ViewGroup.LayoutParams.WRAP_CONTENT,
723-
)
724-
}
725-
726-
appBar.removeView(titleToolbar)
727-
appBar.removeView(actionsToolbar)
728-
729-
titleToolbar.layoutParams =
730-
LinearLayout.LayoutParams(
731-
0,
732-
ViewGroup.LayoutParams.WRAP_CONTENT,
733-
1f,
734-
)
735-
actionsToolbar.layoutParams =
736-
LinearLayout
737-
.LayoutParams(
738-
ViewGroup.LayoutParams.WRAP_CONTENT,
739-
ViewGroup.LayoutParams.WRAP_CONTENT,
740-
).apply { marginEnd = SizeUtils.dp2px(8f) }
741-
742-
content.root.findViewById<TextView>(R.id.title_text)?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
743-
marginEnd = SizeUtils.dp2px(8f)
744-
}
745-
746-
row.addView(titleToolbar)
747-
row.addView(actionsToolbar)
748-
appBar.addView(row, insertAt)
749-
return
750-
}
751-
752-
if (!isLandscape && titleParent is LinearLayout && titleParent.parent === appBar) {
753-
val row = titleParent
754-
val insertAt = appBar.indexOfChild(row).coerceAtLeast(0)
755-
row.removeView(titleToolbar)
756-
row.removeView(actionsToolbar)
757-
appBar.removeView(row)
758-
759-
titleToolbar.layoutParams =
760-
com.google.android.material.appbar.AppBarLayout.LayoutParams(
761-
ViewGroup.LayoutParams.MATCH_PARENT,
762-
ViewGroup.LayoutParams.WRAP_CONTENT,
763-
)
764-
actionsToolbar.layoutParams =
765-
com.google.android.material.appbar.AppBarLayout
766-
.LayoutParams(
767-
ViewGroup.LayoutParams.MATCH_PARENT,
768-
ViewGroup.LayoutParams.WRAP_CONTENT,
769-
).apply {
770-
topMargin = SizeUtils.dp2px(4f)
771-
}
772-
773-
content.root.findViewById<TextView>(R.id.title_text)?.updateLayoutParams<ViewGroup.MarginLayoutParams> {
774-
marginEnd = SizeUtils.dp2px(16f)
775-
}
776-
777-
appBar.addView(titleToolbar, insertAt)
778-
appBar.addView(actionsToolbar, insertAt + 1)
779-
}
780-
}
781-
782695
private fun onSwipeRevealDragProgress(progress: Float) {
783696
_binding?.apply {
784697
contentCard.progress = progress

app/src/main/java/com/itsaky/androidide/adapters/TemplateListAdapter.kt

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ package com.itsaky.androidide.adapters
2020
import android.view.LayoutInflater
2121
import android.view.View
2222
import android.view.ViewGroup
23-
import androidx.recyclerview.widget.DiffUtil
2423
import androidx.recyclerview.widget.RecyclerView
2524
import com.blankj.utilcode.util.ConvertUtils
2625
import com.google.android.material.shape.CornerFamily
@@ -81,39 +80,11 @@ class TemplateListAdapter(
8180
}
8281

8382
root.setOnLongClickListener {
84-
template.tooltipTag?.let { tag ->
83+
template.tooltipTag?.let { _ ->
8584
onLongClick?.invoke(template, it)
8685
}
8786
true // Consume the event
8887
}
8988
}
9089
}
91-
92-
internal fun fillDiff(extras: Int) {
93-
val count = itemCount
94-
for (i in 1..extras) {
95-
templates.add(Template.EMPTY)
96-
}
97-
98-
val diff =
99-
DiffUtil.calculateDiff(
100-
object : DiffUtil.Callback() {
101-
override fun getOldListSize(): Int = count
102-
103-
override fun getNewListSize(): Int = count + extras
104-
105-
override fun areItemsTheSame(
106-
oldItemPosition: Int,
107-
newItemPosition: Int,
108-
): Boolean = newItemPosition < count && oldItemPosition == newItemPosition
109-
110-
override fun areContentsTheSame(
111-
oldItemPosition: Int,
112-
newItemPosition: Int,
113-
): Boolean = areItemsTheSame(oldItemPosition, newItemPosition)
114-
},
115-
)
116-
117-
diff.dispatchUpdatesTo(this)
118-
}
11990
}

app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt

Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,19 @@ package com.itsaky.androidide.fragments
1919

2020
import android.os.Bundle
2121
import android.view.View
22-
import android.view.ViewTreeObserver.OnGlobalLayoutListener
22+
import android.content.res.Configuration
2323
import androidx.core.view.ViewCompat
2424
import androidx.core.view.WindowInsetsCompat
2525
import androidx.core.view.updatePadding
2626
import androidx.fragment.app.viewModels
27-
import com.google.android.flexbox.FlexDirection
28-
import com.google.android.flexbox.FlexboxLayoutManager
29-
import com.google.android.flexbox.JustifyContent
27+
import androidx.recyclerview.widget.GridLayoutManager
3028
import com.itsaky.androidide.R
3129
import com.itsaky.androidide.adapters.TemplateListAdapter
3230
import com.itsaky.androidide.databinding.FragmentTemplateListBinding
3331
import com.itsaky.androidide.idetooltips.TooltipManager
3432
import com.itsaky.androidide.idetooltips.TooltipTag.EXIT_TO_MAIN
3533
import com.itsaky.androidide.templates.ITemplateProvider
3634
import com.itsaky.androidide.templates.ProjectTemplate
37-
import com.itsaky.androidide.utils.FlexboxUtils
3835
import com.itsaky.androidide.viewmodel.MainViewModel
3936
import org.slf4j.LoggerFactory
4037

@@ -49,9 +46,6 @@ class TemplateListFragment :
4946
FragmentTemplateListBinding::bind,
5047
) {
5148
private var adapter: TemplateListAdapter? = null
52-
private var layoutManager: FlexboxLayoutManager? = null
53-
54-
private lateinit var globalLayoutListener: OnGlobalLayoutListener
5549

5650
private val viewModel by viewModels<MainViewModel>(ownerProducer = { requireActivity() })
5751

@@ -65,41 +59,27 @@ class TemplateListFragment :
6559
) {
6660
super.onViewCreated(view, savedInstanceState)
6761

68-
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets ->
69-
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
70-
v.updatePadding(bottom = insets.bottom)
71-
windowInsets
72-
}
73-
74-
layoutManager = FlexboxLayoutManager(requireContext(), FlexDirection.ROW)
75-
layoutManager!!.justifyContent = JustifyContent.SPACE_EVENLY
76-
77-
binding.list.layoutManager = layoutManager
78-
79-
// This makes sure that the items are evenly distributed in the list
80-
// and the last row is always aligned to the start
81-
globalLayoutListener =
82-
FlexboxUtils.createGlobalLayoutListenerToDistributeFlexboxItemsEvenly(
83-
{ adapter },
84-
{ layoutManager },
85-
) { adapter, diff ->
86-
adapter.fillDiff(diff)
87-
}
62+
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets ->
63+
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
64+
v.updatePadding(bottom = insets.bottom)
65+
windowInsets
66+
}
8867

89-
binding.list.viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener)
68+
val gridLayoutManager = GridLayoutManager(requireContext(), 1)
69+
binding.list.layoutManager = gridLayoutManager
9070

9171
binding.exitButton.setOnClickListener {
9272
viewModel.setScreen(MainViewModel.SCREEN_MAIN)
9373
}
9474

95-
binding.exitButton.setOnLongClickListener {
96-
TooltipManager.showIdeCategoryTooltip(
97-
context = requireContext(),
98-
anchorView = binding.root,
99-
tag = EXIT_TO_MAIN,
100-
)
101-
true
102-
}
75+
binding.exitButton.setOnLongClickListener {
76+
TooltipManager.showIdeCategoryTooltip(
77+
context = requireContext(),
78+
anchorView = binding.root,
79+
tag = EXIT_TO_MAIN,
80+
)
81+
true
82+
}
10383

10484
viewModel.currentScreen.observe(viewLifecycleOwner) { current ->
10585
if (current == MainViewModel.SCREEN_TEMPLATE_DETAILS) {
@@ -110,8 +90,32 @@ class TemplateListFragment :
11090
}
11191
}
11292

93+
override fun onConfigurationChanged(newConfig: Configuration) {
94+
super.onConfigurationChanged(newConfig)
95+
96+
updateSpanCount()
97+
}
98+
99+
override fun onResume() {
100+
super.onResume()
101+
updateSpanCount()
102+
}
103+
104+
private fun updateSpanCount() {
105+
val isLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
106+
val maxSpans = if (isLandscape) 6 else 4
107+
108+
val itemCount = binding.list.adapter?.itemCount ?: 0
109+
110+
val optimalSpans = maxOf(1, minOf(maxSpans, itemCount))
111+
112+
val layoutManager = binding.list.layoutManager as? GridLayoutManager
113+
if (layoutManager != null && layoutManager.spanCount != optimalSpans) {
114+
layoutManager.spanCount = optimalSpans
115+
}
116+
}
117+
113118
override fun onDestroyView() {
114-
binding.list.viewTreeObserver.removeOnGlobalLayoutListener(globalLayoutListener)
115119
super.onDestroyView()
116120
}
117121

@@ -138,16 +142,15 @@ class TemplateListFragment :
138142
},
139143
onLongClick = { template, itemView ->
140144
template.tooltipTag?.let { tag ->
141-
TooltipManager.showIdeCategoryTooltip(
142-
context = requireContext(),
143-
anchorView = itemView,
144-
tag = tag
145-
)
146-
}
147-
},
145+
TooltipManager.showIdeCategoryTooltip(
146+
context = requireContext(),
147+
anchorView = itemView,
148+
tag = tag
149+
)
150+
}
151+
},
148152
)
149-
150153
binding.list.adapter = adapter
154+
updateSpanCount()
151155
}
152-
153156
}

app/src/main/res/layout-land/content_editor.xml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,18 @@
3232
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior">
3333

3434
<!-- Landscape: project name and project actions toolbar in one row; long name truncates so toolbar stays visible -->
35-
<LinearLayout
35+
<com.google.android.flexbox.FlexboxLayout
3636
android:layout_width="match_parent"
3737
android:layout_height="wrap_content"
38-
android:orientation="horizontal"
39-
android:gravity="center_vertical">
38+
app:flexWrap="wrap"
39+
app:alignItems="center">
4040

4141
<com.google.android.material.appbar.MaterialToolbar
4242
android:id="@+id/title_toolbar"
43-
android:layout_width="0dp"
43+
android:layout_width="wrap_content"
4444
android:layout_height="wrap_content"
45-
android:layout_weight="1"
45+
app:layout_flexGrow="1"
46+
app:layout_flexShrink="0"
4647
android:minHeight="0dp"
4748
android:padding="0dp"
4849
app:contentInsetStartWithNavigation="0dp"
@@ -69,11 +70,12 @@
6970
android:layout_width="wrap_content"
7071
android:layout_height="wrap_content"
7172
android:layout_marginEnd="8dp"
73+
app:layout_flexShrink="0"
7274
android:minHeight="0dp"
7375
android:paddingTop="0dp"
7476
android:paddingBottom="0dp" />
7577

76-
</LinearLayout>
78+
</com.google.android.flexbox.FlexboxLayout>
7779

7880
<com.google.android.material.progressindicator.LinearProgressIndicator
7981
android:id="@+id/progress_indicator"

0 commit comments

Comments
 (0)