From b981cfa736cf238e2bebbee3b3d42a6270517993 Mon Sep 17 00:00:00 2001 From: John Trujillo Date: Mon, 9 Mar 2026 10:41:11 -0500 Subject: [PATCH 1/2] 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 558068dda6..2651e35bc6 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 @@ -596,7 +594,6 @@ abstract class BaseEditorActivity : } setupToolbar() - syncProjectToolbarRowForOrientation(resources.configuration.orientation) setupDrawers() content.tabs.addOnTabSelectedListener(this) @@ -634,7 +631,6 @@ abstract class BaseEditorActivity : override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - syncProjectToolbarRowForOrientation(newConfig.orientation) } private fun setupToolbar() { @@ -688,90 +684,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/2] 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 @@