From 8c21fbc00236259f266fb7f0057a5ea6949cfd76 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Fri, 25 Aug 2023 17:01:11 +0530 Subject: [PATCH] feat: add UI for build variants list --- .../sidebar/BuildVariantsSidebarAction.kt | 42 ++++++++++++ .../adapters/BuildVariantsAdapter.kt | 65 +++++++++++++++++++ .../fragments/RecyclerViewFragment.kt | 12 +++- .../sidebar/BuildVariantsFragment.kt | 49 ++++++++++++++ .../androidide/utils/EditorSidebarActions.kt | 52 +++------------ .../androidide/viewmodel/EditorViewModel.kt | 9 +++ .../res/layout/layout_build_variant_item.xml | 54 +++++++++++++++ resources/src/main/res/values/strings.xml | 1 + .../tooling/api/models/BuildVariantInfo.kt | 14 ++-- 9 files changed, 244 insertions(+), 54 deletions(-) create mode 100644 app/src/main/java/com/itsaky/androidide/actions/sidebar/BuildVariantsSidebarAction.kt create mode 100644 app/src/main/java/com/itsaky/androidide/adapters/BuildVariantsAdapter.kt create mode 100644 app/src/main/java/com/itsaky/androidide/fragments/sidebar/BuildVariantsFragment.kt create mode 100644 app/src/main/res/layout/layout_build_variant_item.xml rename app/src/main/java/com/itsaky/androidide/fragments/sidebar/BulidVariantsFragment.kt => subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/BuildVariantInfo.kt (59%) diff --git a/app/src/main/java/com/itsaky/androidide/actions/sidebar/BuildVariantsSidebarAction.kt b/app/src/main/java/com/itsaky/androidide/actions/sidebar/BuildVariantsSidebarAction.kt new file mode 100644 index 000000000..8a503f6d9 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/actions/sidebar/BuildVariantsSidebarAction.kt @@ -0,0 +1,42 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.actions.sidebar + +import android.content.Context +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import com.itsaky.androidide.R +import com.itsaky.androidide.fragments.sidebar.BuildVariantsFragment +import kotlin.reflect.KClass + +/** + * Sidebar action for showing the build variants of Android modules in the project. + * + * @author Akash Yadav + */ +class BuildVariantsSidebarAction(context: Context, override val order: Int) : + AbstractSidebarAction() { + + override val fragmentClass: KClass = BuildVariantsFragment::class + override val id: String = "ide.editor.sidebar.buildVariants" + + init { + label = context.getString(R.string.title_build_variants) + icon = ContextCompat.getDrawable(context, R.drawable.ic_android) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/itsaky/androidide/adapters/BuildVariantsAdapter.kt b/app/src/main/java/com/itsaky/androidide/adapters/BuildVariantsAdapter.kt new file mode 100644 index 000000000..11ab53fe2 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/adapters/BuildVariantsAdapter.kt @@ -0,0 +1,65 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ArrayAdapter +import androidx.recyclerview.widget.RecyclerView +import com.itsaky.androidide.R +import com.itsaky.androidide.databinding.LayoutBuildVariantItemBinding +import com.itsaky.androidide.tooling.api.models.BuildVariantInfo + +/** + * [RecyclerView] adapter for showing the list of Android modules and their selected build variant. + * + * @property items + * @author Akash Yadav + */ +class BuildVariantsAdapter( + private val items: List +) : RecyclerView.Adapter() { + + + class ViewHolder(internal val binding: LayoutBuildVariantItemBinding) : + RecyclerView.ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = LayoutBuildVariantItemBinding.inflate(LayoutInflater.from(parent.context), parent, + false) + return ViewHolder(binding) + } + + override fun getItemCount(): Int { + return items.size + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val binding = holder.binding + val variantInfo = items[position] + + binding.moduleName.text = variantInfo.modulePath + binding.variantName.setAdapter( + ArrayAdapter(binding.root.context, R.layout.support_simple_spinner_dropdown_item, + variantInfo.buildVariants + ) + ) + + binding.variantName.listSelection = variantInfo.selectedVariantIndex + } +} \ No newline at end of file diff --git a/app/src/main/java/com/itsaky/androidide/fragments/RecyclerViewFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/RecyclerViewFragment.kt index bb7286481..aba447bf9 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/RecyclerViewFragment.kt +++ b/app/src/main/java/com/itsaky/androidide/fragments/RecyclerViewFragment.kt @@ -22,7 +22,6 @@ import android.view.View import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.LayoutManager -import com.itsaky.androidide.R import com.itsaky.androidide.databinding.FragmentRecyclerviewBinding /** @@ -47,12 +46,19 @@ abstract class RecyclerViewFragment> : return LinearLayoutManager(requireContext()) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + /** + * Sets up the recycler view in the fragment. + */ + protected open fun onSetupRecyclerView() { binding.root.apply { layoutManager = onCreateLayoutManager() adapter = unsavedAdapter ?: onCreateAdapter() } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + onSetupRecyclerView() unsavedAdapter = null diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sidebar/BuildVariantsFragment.kt b/app/src/main/java/com/itsaky/androidide/fragments/sidebar/BuildVariantsFragment.kt new file mode 100644 index 000000000..e6346a996 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/fragments/sidebar/BuildVariantsFragment.kt @@ -0,0 +1,49 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.fragments.sidebar + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.RecyclerView +import com.itsaky.androidide.adapters.BuildVariantsAdapter +import com.itsaky.androidide.fragments.RecyclerViewFragment +import com.itsaky.androidide.viewmodel.EditorViewModel + +/** + * A fragment to show the list of Android modules and its build variants. + * + * @author Akash Yadav + */ +class BuildVariantsFragment : RecyclerViewFragment() { + + private val viewModel by viewModels(ownerProducer = { requireActivity() }) + + override fun onCreateAdapter(): RecyclerView.Adapter<*> { + return BuildVariantsAdapter(viewModel.buildVariants) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel._buildVariants.observe(viewLifecycleOwner) { + + // Setup the recyclerview each time build variants list is updated + onSetupRecyclerView() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/itsaky/androidide/utils/EditorSidebarActions.kt b/app/src/main/java/com/itsaky/androidide/utils/EditorSidebarActions.kt index ca44da911..1d1649202 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/EditorSidebarActions.kt +++ b/app/src/main/java/com/itsaky/androidide/utils/EditorSidebarActions.kt @@ -40,10 +40,12 @@ import com.itsaky.androidide.actions.ActionsRegistry import com.itsaky.androidide.actions.FillMenuParams import com.itsaky.androidide.actions.SidebarActionItem import com.itsaky.androidide.actions.internal.DefaultActionsRegistry +import com.itsaky.androidide.actions.sidebar.BuildVariantsSidebarAction import com.itsaky.androidide.actions.sidebar.CloseProjectSidebarAction import com.itsaky.androidide.actions.sidebar.FileTreeSidebarAction import com.itsaky.androidide.actions.sidebar.PreferencesSidebarAction import com.itsaky.androidide.actions.sidebar.TerminalSidebarAction +import com.itsaky.androidide.fragments.sidebar.BuildVariantsFragment import com.itsaky.androidide.fragments.sidebar.EditorSidebarFragment import java.lang.ref.WeakReference @@ -60,7 +62,10 @@ internal object EditorSidebarActions { fun registerActions(context: Context) { val registry = ActionsRegistry.getInstance() var order = -1 + + @Suppress("KotlinConstantConditions") registry.registerAction(FileTreeSidebarAction(context, ++order)) + registry.registerAction(BuildVariantsSidebarAction(context, ++order)) registry.registerAction(TerminalSidebarAction(context, ++order)) registry.registerAction(PreferencesSidebarAction(context, ++order)) registry.registerAction(CloseProjectSidebarAction(context, ++order)) @@ -88,6 +93,7 @@ internal object EditorSidebarActions { val data = ActionData() data.put(Context::class.java, context) // needed for inflating the menu + val titleRef = WeakReference(binding.title) val params = FillMenuParams(data, ActionItem.Location.EDITOR_SIDEBAR, rail.menu) { actionsRegistry, action, item, actionsData -> @@ -104,18 +110,15 @@ internal object EditorSidebarActions { controller.navigate(action.id, navOptions { launchSingleTop = true restoreState = true - - popUpTo(controller.graph.findStartDestination().id) { - inclusive = false - saveState = true - } }) // Return true only if the destination we've navigated to matches the MenuItem val result = controller.currentDestination?.matchDestination(action.id) == true if (result) { item.isChecked = true + titleRef.get()?.text = item.title } + result } catch (e: IllegalArgumentException) { false @@ -147,36 +150,6 @@ internal object EditorSidebarActions { destination(builder) } } - - val railRef = WeakReference(rail) - val titleRef = WeakReference(binding.title) - controller.addOnDestinationChangedListener( - object : NavController.OnDestinationChangedListener { - override fun onDestinationChanged( - controller: NavController, - destination: NavDestination, - arguments: Bundle? - ) { - val railView = railRef.get() - if (railView == null) { - controller.removeOnDestinationChangedListener(this) - return - } - - railView.menu.forEach { item -> - if (destination.matchDestination(item.itemId)) { - item.isChecked = true - titleRef.get()?.text = item.title - } - } - } - }) - - // make sure the 'File tree' item is checked by default - rail.menu.findItem(FileTreeSidebarAction.ID.hashCode())?.also { - it.isChecked = true - binding.title.text = it.title - } } /** @@ -188,15 +161,6 @@ internal object EditorSidebarActions { internal fun NavDestination.matchDestination(route: String): Boolean = hierarchy.any { it.route == route } - /** - * Determines whether the given `destId` matches the NavDestination. This handles - * both the default case (the destination's id matches the given id) and the nested case where - * the given id is a parent/grandparent/etc of the destination. - */ - @JvmStatic - internal fun NavDestination.matchDestination(@IdRes destId: Int): Boolean = - hierarchy.any { it.id == destId } - @JvmStatic internal fun ShapeAppearanceModel.roundedOnRight(cornerSize: Float = 28f): ShapeAppearanceModel { return toBuilder().run { diff --git a/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.kt b/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.kt index f5b3b0be1..ab7705661 100644 --- a/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.kt +++ b/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.kt @@ -27,6 +27,7 @@ import com.google.gson.GsonBuilder import com.itsaky.androidide.models.OpenedFilesCache import com.itsaky.androidide.projects.ProjectManager import com.itsaky.androidide.tasks.executeAsync +import com.itsaky.androidide.tooling.api.models.BuildVariantInfo import com.itsaky.androidide.utils.Environment import java.io.File @@ -52,6 +53,8 @@ class EditorViewModel : ViewModel() { */ private val mCurrentFile = MutableLiveData?>(null) + internal val _buildVariants = MutableLiveData>(null) + var openedFilesCache: OpenedFilesCache? get() = _openedFiles.value set(value) { @@ -100,6 +103,12 @@ class EditorViewModel : ViewModel() { _startDrawerOpened.value = value } + var buildVariants: List + get() = this._buildVariants.value ?: emptyList() + set(value) { + this._buildVariants.value = value + } + /** * Add the given file to the list of opened files. * diff --git a/app/src/main/res/layout/layout_build_variant_item.xml b/app/src/main/res/layout/layout_build_variant_item.xml new file mode 100644 index 000000000..c192c5fc6 --- /dev/null +++ b/app/src/main/res/layout/layout_build_variant_item.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index c319dcb7f..487780713 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -594,4 +594,5 @@ Failed to perform code action Enable LogSender When disabled, logs from applications won\'t be shown in AndroidIDE + Build variants diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sidebar/BulidVariantsFragment.kt b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/BuildVariantInfo.kt similarity index 59% rename from app/src/main/java/com/itsaky/androidide/fragments/sidebar/BulidVariantsFragment.kt rename to subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/BuildVariantInfo.kt index a46b7cd64..94e2fb98b 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sidebar/BulidVariantsFragment.kt +++ b/subprojects/tooling-api-model/src/main/java/com/itsaky/androidide/tooling/api/models/BuildVariantInfo.kt @@ -15,15 +15,15 @@ * along with AndroidIDE. If not, see . */ -package com.itsaky.androidide.fragments.sidebar - -import com.itsaky.androidide.fragments.EmptyStateFragment -import com.itsaky.androidide.fragments.RecyclerViewFragment +package com.itsaky.androidide.tooling.api.models /** - * A fragment to show the list of modules and the selected build variants in the + * Information about the build variants of an Android module. * + * @property modulePath The project path of the Android module project. + * @property buildVariants The build variants of the Android module project. + * @property selectedVariantIndex The index of the build variant in the [buildVariants] list which is currently selected. * @author Akash Yadav */ -class BulidVariantsFragment { -} \ No newline at end of file +data class BuildVariantInfo(val modulePath: String, val buildVariants: List, + val selectedVariantIndex: Int)