From bd8fc5d1749cf4eac88f6d159069ad7078b9fdf0 Mon Sep 17 00:00:00 2001 From: John Trujillo Date: Mon, 9 Mar 2026 10:41:11 -0500 Subject: [PATCH 1/5] feat(ui): implement adaptive navigation patterns for toolbars and template list --- .../activities/editor/BaseEditorActivity.kt | 88 ------------------- .../adapters/TemplateListAdapter.kt | 31 +------ .../fragments/TemplateListFragment.kt | 84 ++++++++---------- app/src/main/res/layout/content_editor.xml | 68 +++++++------- .../res/layout/layout_template_list_item.xml | 4 +- 5 files changed, 78 insertions(+), 197 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt index bb8bf093bf..35fdfe0d94 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt @@ -41,8 +41,6 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver.OnGlobalLayoutListener -import android.widget.LinearLayout -import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultLauncher @@ -603,7 +601,6 @@ abstract class BaseEditorActivity : } setupToolbar() - syncProjectToolbarRowForOrientation(resources.configuration.orientation) setupDrawers() content.tabs.addOnTabSelectedListener(this) @@ -641,7 +638,6 @@ abstract class BaseEditorActivity : override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - syncProjectToolbarRowForOrientation(newConfig.orientation) } private fun setupToolbar() { @@ -695,90 +691,6 @@ abstract class BaseEditorActivity : } } - private fun syncProjectToolbarRowForOrientation(currentOrientation: Int) { - val appBar = content.editorAppBarLayout - val titleToolbar = content.titleToolbar - val actionsToolbar = content.projectActionsToolbar - - val titleParent = titleToolbar.parent as? ViewGroup ?: return - val actionsParent = actionsToolbar.parent as? ViewGroup ?: return - if (titleParent != actionsParent) return - - val isLandscape = currentOrientation == Configuration.ORIENTATION_LANDSCAPE - - if (isLandscape && titleParent === appBar) { - val insertAt = - minOf( - appBar.indexOfChild(titleToolbar), - appBar.indexOfChild(actionsToolbar), - ).coerceAtLeast(0) - val row = - LinearLayout(this).apply { - orientation = LinearLayout.HORIZONTAL - gravity = Gravity.CENTER_VERTICAL - layoutParams = - com.google.android.material.appbar.AppBarLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - ) - } - - appBar.removeView(titleToolbar) - appBar.removeView(actionsToolbar) - - titleToolbar.layoutParams = - LinearLayout.LayoutParams( - 0, - ViewGroup.LayoutParams.WRAP_CONTENT, - 1f, - ) - actionsToolbar.layoutParams = - LinearLayout - .LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - ).apply { marginEnd = SizeUtils.dp2px(8f) } - - content.root.findViewById(R.id.title_text)?.updateLayoutParams { - marginEnd = SizeUtils.dp2px(8f) - } - - row.addView(titleToolbar) - row.addView(actionsToolbar) - appBar.addView(row, insertAt) - return - } - - if (!isLandscape && titleParent is LinearLayout && titleParent.parent === appBar) { - val row = titleParent - val insertAt = appBar.indexOfChild(row).coerceAtLeast(0) - row.removeView(titleToolbar) - row.removeView(actionsToolbar) - appBar.removeView(row) - - titleToolbar.layoutParams = - com.google.android.material.appbar.AppBarLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - ) - actionsToolbar.layoutParams = - com.google.android.material.appbar.AppBarLayout - .LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - ).apply { - topMargin = SizeUtils.dp2px(4f) - } - - content.root.findViewById(R.id.title_text)?.updateLayoutParams { - marginEnd = SizeUtils.dp2px(16f) - } - - appBar.addView(titleToolbar, insertAt) - appBar.addView(actionsToolbar, insertAt + 1) - } - } - private fun onSwipeRevealDragProgress(progress: Float) { _binding?.apply { contentCard.progress = progress diff --git a/app/src/main/java/com/itsaky/androidide/adapters/TemplateListAdapter.kt b/app/src/main/java/com/itsaky/androidide/adapters/TemplateListAdapter.kt index a4baf5c4f1..5f6616b31a 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/TemplateListAdapter.kt +++ b/app/src/main/java/com/itsaky/androidide/adapters/TemplateListAdapter.kt @@ -20,7 +20,6 @@ package com.itsaky.androidide.adapters import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.blankj.utilcode.util.ConvertUtils import com.google.android.material.shape.CornerFamily @@ -81,39 +80,11 @@ class TemplateListAdapter( } root.setOnLongClickListener { - template.tooltipTag?.let { tag -> + template.tooltipTag?.let { _ -> onLongClick?.invoke(template, it) } true // Consume the event } } } - - internal fun fillDiff(extras: Int) { - val count = itemCount - for (i in 1..extras) { - templates.add(Template.EMPTY) - } - - val diff = - DiffUtil.calculateDiff( - object : DiffUtil.Callback() { - override fun getOldListSize(): Int = count - - override fun getNewListSize(): Int = count + extras - - override fun areItemsTheSame( - oldItemPosition: Int, - newItemPosition: Int, - ): Boolean = newItemPosition < count && oldItemPosition == newItemPosition - - override fun areContentsTheSame( - oldItemPosition: Int, - newItemPosition: Int, - ): Boolean = areItemsTheSame(oldItemPosition, newItemPosition) - }, - ) - - diff.dispatchUpdatesTo(this) - } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt index 62d9967ea2..0fb1deba08 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt @@ -19,14 +19,12 @@ package com.itsaky.androidide.fragments import android.os.Bundle import android.view.View -import android.view.ViewTreeObserver.OnGlobalLayoutListener +import android.content.res.Configuration import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import androidx.fragment.app.viewModels -import com.google.android.flexbox.FlexDirection -import com.google.android.flexbox.FlexboxLayoutManager -import com.google.android.flexbox.JustifyContent +import androidx.recyclerview.widget.GridLayoutManager import com.itsaky.androidide.R import com.itsaky.androidide.adapters.TemplateListAdapter import com.itsaky.androidide.databinding.FragmentTemplateListBinding @@ -34,7 +32,6 @@ import com.itsaky.androidide.idetooltips.TooltipManager import com.itsaky.androidide.idetooltips.TooltipTag.EXIT_TO_MAIN import com.itsaky.androidide.templates.ITemplateProvider import com.itsaky.androidide.templates.ProjectTemplate -import com.itsaky.androidide.utils.FlexboxUtils import com.itsaky.androidide.viewmodel.MainViewModel import org.slf4j.LoggerFactory @@ -49,9 +46,6 @@ class TemplateListFragment : FragmentTemplateListBinding::bind, ) { private var adapter: TemplateListAdapter? = null - private var layoutManager: FlexboxLayoutManager? = null - - private lateinit var globalLayoutListener: OnGlobalLayoutListener private val viewModel by viewModels(ownerProducer = { requireActivity() }) @@ -65,41 +59,31 @@ class TemplateListFragment : ) { super.onViewCreated(view, savedInstanceState) - ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets -> - val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) - v.updatePadding(bottom = insets.bottom) - windowInsets - } - - layoutManager = FlexboxLayoutManager(requireContext(), FlexDirection.ROW) - layoutManager!!.justifyContent = JustifyContent.SPACE_EVENLY - - binding.list.layoutManager = layoutManager - - // This makes sure that the items are evenly distributed in the list - // and the last row is always aligned to the start - globalLayoutListener = - FlexboxUtils.createGlobalLayoutListenerToDistributeFlexboxItemsEvenly( - { adapter }, - { layoutManager }, - ) { adapter, diff -> - adapter.fillDiff(diff) - } + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.updatePadding(bottom = insets.bottom) + windowInsets + } + + val screenWidthDp = resources.configuration.screenWidthDp + val minItemWidthDp = 160 + val initialSpans = (screenWidthDp / minItemWidthDp).coerceIn(1, 4) - binding.list.viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener) + val gridLayoutManager = GridLayoutManager(requireContext(), initialSpans) + binding.list.layoutManager = gridLayoutManager binding.exitButton.setOnClickListener { viewModel.setScreen(MainViewModel.SCREEN_MAIN) } - binding.exitButton.setOnLongClickListener { - TooltipManager.showIdeCategoryTooltip( - context = requireContext(), - anchorView = binding.root, - tag = EXIT_TO_MAIN, - ) - true - } + binding.exitButton.setOnLongClickListener { + TooltipManager.showIdeCategoryTooltip( + context = requireContext(), + anchorView = binding.root, + tag = EXIT_TO_MAIN, + ) + true + } viewModel.currentScreen.observe(viewLifecycleOwner) { current -> if (current == MainViewModel.SCREEN_TEMPLATE_DETAILS) { @@ -110,8 +94,16 @@ class TemplateListFragment : } } + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + + val minItemWidthDp = 160 + val optimalSpans = (newConfig.screenWidthDp / minItemWidthDp).coerceIn(1, 4) + + (binding.list.layoutManager as? GridLayoutManager)?.spanCount = optimalSpans + } + override fun onDestroyView() { - binding.list.viewTreeObserver.removeOnGlobalLayoutListener(globalLayoutListener) super.onDestroyView() } @@ -138,16 +130,14 @@ class TemplateListFragment : }, onLongClick = { template, itemView -> template.tooltipTag?.let { tag -> - TooltipManager.showIdeCategoryTooltip( - context = requireContext(), - anchorView = itemView, - tag = tag - ) - } - }, + TooltipManager.showIdeCategoryTooltip( + context = requireContext(), + anchorView = itemView, + tag = tag + ) + } + }, ) - binding.list.adapter = adapter } - } diff --git a/app/src/main/res/layout/content_editor.xml b/app/src/main/res/layout/content_editor.xml index f3f10df9fb..ff4415253f 100644 --- a/app/src/main/res/layout/content_editor.xml +++ b/app/src/main/res/layout/content_editor.xml @@ -31,40 +31,48 @@ android:fitsSystemWindows="false" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior"> - - - + + + + + + + - - - - + android:paddingBottom="0dp" /> + Date: Tue, 10 Mar 2026 08:47:56 -0500 Subject: [PATCH 2/5] 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. --- .../fragments/TemplateListFragment.kt | 24 ++++++++++++------- .../res/layout/project_actions_toolbar.xml | 4 ++-- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt index 0fb1deba08..4ed280260b 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt @@ -65,11 +65,7 @@ class TemplateListFragment : windowInsets } - val screenWidthDp = resources.configuration.screenWidthDp - val minItemWidthDp = 160 - val initialSpans = (screenWidthDp / minItemWidthDp).coerceIn(1, 4) - - val gridLayoutManager = GridLayoutManager(requireContext(), initialSpans) + val gridLayoutManager = GridLayoutManager(requireContext(), 1) binding.list.layoutManager = gridLayoutManager binding.exitButton.setOnClickListener { @@ -97,10 +93,21 @@ class TemplateListFragment : override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - val minItemWidthDp = 160 - val optimalSpans = (newConfig.screenWidthDp / minItemWidthDp).coerceIn(1, 4) + updateSpanCount() + } + + private fun updateSpanCount() { + val isLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + val maxSpans = if (isLandscape) 6 else 4 - (binding.list.layoutManager as? GridLayoutManager)?.spanCount = optimalSpans + val itemCount = binding.list.adapter?.itemCount ?: 0 + + val optimalSpans = maxOf(1, minOf(maxSpans, itemCount)) + + val layoutManager = binding.list.layoutManager as? GridLayoutManager + if (layoutManager != null && layoutManager.spanCount != optimalSpans) { + layoutManager.spanCount = optimalSpans + } } override fun onDestroyView() { @@ -139,5 +146,6 @@ class TemplateListFragment : }, ) binding.list.adapter = adapter + updateSpanCount() } } diff --git a/common/src/main/res/layout/project_actions_toolbar.xml b/common/src/main/res/layout/project_actions_toolbar.xml index 0088cf3d07..900b8fd56a 100644 --- a/common/src/main/res/layout/project_actions_toolbar.xml +++ b/common/src/main/res/layout/project_actions_toolbar.xml @@ -5,7 +5,7 @@ app:contentInsetStartWithNavigation="0dp" app:contentInsetStart="0dp" app:contentInsetEnd="0dp" - android:layout_width="match_parent" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:minHeight="0dp" android:paddingTop="0dp" @@ -13,7 +13,7 @@ Date: Wed, 11 Mar 2026 12:17:51 -0500 Subject: [PATCH 3/5] fix: adapt toolbar in tablets and DeX mode --- .../activities/editor/BaseEditorActivity.kt | 3 ++- app/src/main/res/layout-land/content_editor.xml | 13 ++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt index 35fdfe0d94..087549ba55 100644 --- a/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt +++ b/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt @@ -41,6 +41,7 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver.OnGlobalLayoutListener +import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultLauncher @@ -642,7 +643,7 @@ abstract class BaseEditorActivity : private fun setupToolbar() { // Set the project name in the title TextView - content.root.findViewById(R.id.title_text)?.apply { + content.root.findViewById(R.id.title_text)?.apply { text = editorViewModel.getProjectName() } diff --git a/app/src/main/res/layout-land/content_editor.xml b/app/src/main/res/layout-land/content_editor.xml index b7ce0c131c..619ca4209e 100644 --- a/app/src/main/res/layout-land/content_editor.xml +++ b/app/src/main/res/layout-land/content_editor.xml @@ -32,16 +32,18 @@ app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior"> - + app:flexWrap="wrap" + app:alignItems="center"> - + Date: Wed, 11 Mar 2026 12:35:48 -0500 Subject: [PATCH 4/5] refactor: remove unused param --- app/src/main/res/layout-land/content_editor.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/res/layout-land/content_editor.xml b/app/src/main/res/layout-land/content_editor.xml index 619ca4209e..20a0336483 100644 --- a/app/src/main/res/layout-land/content_editor.xml +++ b/app/src/main/res/layout-land/content_editor.xml @@ -44,7 +44,6 @@ android:layout_height="wrap_content" app:layout_flexGrow="1" app:layout_flexShrink="0" - android:layout_weight="1" android:minHeight="0dp" android:padding="0dp" app:contentInsetStartWithNavigation="0dp" From 2f409c0a3d602ff1e7a1e4f92450260a531c9d2f Mon Sep 17 00:00:00 2001 From: John Trujillo Date: Wed, 11 Mar 2026 12:52:28 -0500 Subject: [PATCH 5/5] refactor: handle configuration changes with `onResume` --- .../com/itsaky/androidide/fragments/TemplateListFragment.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt index 4ed280260b..f0342a8fb9 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/TemplateListFragment.kt @@ -96,6 +96,11 @@ class TemplateListFragment : updateSpanCount() } + override fun onResume() { + super.onResume() + updateSpanCount() + } + private fun updateSpanCount() { val isLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE val maxSpans = if (isLandscape) 6 else 4