mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
feat: add UI for build variants list
This commit is contained in:
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<out Fragment> = 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)
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<BuildVariantInfo>
|
||||
) : RecyclerView.Adapter<BuildVariantsAdapter.ViewHolder>() {
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<A : RecyclerView.Adapter<*>> :
|
||||
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
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<BuildVariantsAdapter>() {
|
||||
|
||||
private val viewModel by viewModels<EditorViewModel>(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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Pair<Int, File?>?>(null)
|
||||
|
||||
internal val _buildVariants = MutableLiveData<List<BuildVariantInfo>>(null)
|
||||
|
||||
var openedFilesCache: OpenedFilesCache?
|
||||
get() = _openedFiles.value
|
||||
set(value) {
|
||||
@@ -100,6 +103,12 @@ class EditorViewModel : ViewModel() {
|
||||
_startDrawerOpened.value = value
|
||||
}
|
||||
|
||||
var buildVariants: List<BuildVariantInfo>
|
||||
get() = this._buildVariants.value ?: emptyList()
|
||||
set(value) {
|
||||
this._buildVariants.value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given file to the list of opened files.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
|
||||
app:layout_constraintBottom_toBottomOf="@id/variant_name_layout"
|
||||
app:layout_constraintEnd_toStartOf="@id/variant_name_layout"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text=":app" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/variant_name_layout"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.Dense.ExposedDropdownMenu"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/module_name"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text=":app">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/variant_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="none" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -594,4 +594,5 @@
|
||||
<string name="msg_codeaction_failed">Failed to perform code action</string>
|
||||
<string name="idepref_devOptions_enableLogsender_title">Enable LogSender</string>
|
||||
<string name="idepref_devOptions_enableLogsender_summary">When disabled, logs from applications won\'t be shown in AndroidIDE</string>
|
||||
<string name="title_build_variants">Build variants</string>
|
||||
</resources>
|
||||
|
||||
+7
-7
@@ -15,15 +15,15 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 {
|
||||
}
|
||||
data class BuildVariantInfo(val modulePath: String, val buildVariants: List<String>,
|
||||
val selectedVariantIndex: Int)
|
||||
Reference in New Issue
Block a user