Skip to content

Commit 1f8fe70

Browse files
committed
feat(ai): polish the Agent chat UX and backend settings panes [ADFA-6214]
Title chats from the backend and queue prompts behind an in-flight title; grow the prompt with scrolling, Shift+Enter to send, folding right-aligned user bubbles, copy actions and a directional sidebar toggle. Gemini/OpenAI settings get Material 3 styling.
1 parent 4fdef5d commit 1f8fe70

67 files changed

Lines changed: 1968 additions & 189 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎plugins/AI-Agent-Gemini/src/main/kotlin/com/itsaky/androidide/plugins/aiagentgemini/settings/GeminiSettingsFragment.kt‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import com.itsaky.androidide.plugins.PluginContext
3232
import com.itsaky.androidide.plugins.aiagentgemini.plugin.GeminiPlugin
3333
import com.itsaky.androidide.plugins.aiagentgemini.R
3434
import com.itsaky.androidide.plugins.aiagentgemini.ui.SecretRevealController
35+
import com.itsaky.androidide.plugins.aiagentgemini.ui.applyPaneStyling
3536
import com.itsaky.androidide.plugins.base.PluginFragmentHelper
3637
import com.itsaky.androidide.plugins.security.KeystoreSecretStore
3738
import com.itsaky.androidide.plugins.services.IdeTooltipService
@@ -40,6 +41,14 @@ import java.text.SimpleDateFormat
4041
import java.util.Date
4142
import java.util.Locale
4243

44+
/** The pane's secondary actions, drawn outlined; each section's Save stays filled. */
45+
private val OUTLINED_BUTTON_IDS = setOf(
46+
R.id.btn_clear_api_key,
47+
R.id.btn_edit_api_key,
48+
R.id.btn_get_free_key,
49+
R.id.btn_refresh_models,
50+
)
51+
4352
/**
4453
* This backend's settings pane, mounted by whichever screen offers a backend selector.
4554
*
@@ -108,6 +117,7 @@ class GeminiSettingsFragment : Fragment() {
108117
GeminiSettingsViewModelFactory { GeminiPlugin.getContext() }
109118
)[GeminiSettingsViewModel::class.java]
110119

120+
view.applyPaneStyling(OUTLINED_BUTTON_IDS)
111121
setupApiKeyUi(view)
112122
setupModelPicker(view, chatModelPicker())
113123
setupModelPicker(view, embeddingModelPicker())
@@ -580,6 +590,7 @@ class GeminiSettingsFragment : Fragment() {
580590
}.isSuccess
581591
}
582592

593+
583594
/**
584595
* Put the dropdown chevron on [box]'s end icon.
585596
*
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.itsaky.androidide.plugins.aiagentgemini.ui
2+
3+
import android.content.res.ColorStateList
4+
import android.graphics.Color
5+
import android.view.View
6+
import android.view.ViewGroup
7+
import androidx.annotation.ColorRes
8+
import androidx.core.content.ContextCompat
9+
import com.google.android.material.button.MaterialButton
10+
import com.google.android.material.divider.MaterialDivider
11+
import com.google.android.material.textfield.TextInputLayout
12+
import com.itsaky.androidide.plugins.aiagentgemini.R
13+
14+
/** How much a pane button stands out: one filled action per section, the rest outlined. */
15+
private enum class ButtonEmphasis { FILLED, OUTLINED }
16+
17+
/**
18+
* Gives every Material button, text field and divider under [this] its Material 3 colours, outline
19+
* and ripple in code. The styles' `app:` items are dropped inside the host, so XML alone leaves
20+
* these controls on the host theme's values.
21+
*
22+
* @param outlinedButtonIds the buttons that are secondary actions; every other button is filled.
23+
*/
24+
internal fun View.applyPaneStyling(outlinedButtonIds: Set<Int>) {
25+
when (this) {
26+
is MaterialButton -> applyEmphasis(
27+
if (id in outlinedButtonIds) ButtonEmphasis.OUTLINED else ButtonEmphasis.FILLED
28+
)
29+
is TextInputLayout -> applyOutline()
30+
is MaterialDivider -> applyHairline()
31+
}
32+
if (this is ViewGroup) {
33+
for (i in 0 until childCount) getChildAt(i).applyPaneStyling(outlinedButtonIds)
34+
}
35+
}
36+
37+
/** Container, label, icon, border and ripple for [emphasis], each with its disabled state. */
38+
private fun MaterialButton.applyEmphasis(emphasis: ButtonEmphasis) {
39+
val filled = emphasis == ButtonEmphasis.FILLED
40+
val content = colors(
41+
if (filled) R.color.plugin_button_filled_content else R.color.plugin_button_outlined_content
42+
)
43+
backgroundTintList = if (filled) {
44+
colors(R.color.plugin_button_filled_container)
45+
} else {
46+
ColorStateList.valueOf(Color.TRANSPARENT)
47+
}
48+
setTextColor(content)
49+
iconTint = content
50+
rippleColor = colors(
51+
if (filled) R.color.plugin_button_filled_ripple else R.color.plugin_button_outlined_ripple
52+
)
53+
strokeColor = colors(R.color.plugin_button_outlined_stroke)
54+
strokeWidth = if (filled) 0 else resources.getDimensionPixelSize(R.dimen.button_stroke_width)
55+
cornerRadius = resources.getDimensionPixelSize(R.dimen.radius_md)
56+
}
57+
58+
/** Outline, corners, hint and end icon of an outlined-box field. */
59+
private fun TextInputLayout.applyOutline() {
60+
setBoxStrokeColorStateList(colors(R.color.plugin_box_stroke))
61+
setBoxStrokeErrorColor(colors(R.color.plugin_error))
62+
val radius = resources.getDimension(R.dimen.radius_md)
63+
setBoxCornerRadii(radius, radius, radius, radius)
64+
val hint = colors(R.color.plugin_text_muted)
65+
defaultHintTextColor = hint
66+
hintTextColor = hint
67+
setEndIconTintList(colors(R.color.plugin_on_surface_variant))
68+
}
69+
70+
private fun MaterialDivider.applyHairline() {
71+
setDividerColorResource(R.color.plugin_outline_variant)
72+
setDividerThicknessResource(R.dimen.divider_thickness)
73+
}
74+
75+
/** Resolved against this view's context, which carries the plugin's resources. */
76+
private fun View.colors(@ColorRes id: Int): ColorStateList =
77+
requireNotNull(ContextCompat.getColorStateList(context, id))
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Text field outline. Focus uses the neutral accent, not the plugin hue, to match the buttons. -->
3+
<selector xmlns:android="http://schemas.android.com/apk/res/android">
4+
<item android:alpha="0.38" android:color="@color/plugin_on_surface" android:state_enabled="false" />
5+
<item android:color="@color/plugin_secondary" android:state_focused="true" />
6+
<item android:color="@color/plugin_on_surface" android:state_hovered="true" />
7+
<item android:color="@color/plugin_outline" />
8+
</selector>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Filled button container: the neutral accent, and M3's 12% on-surface wash while disabled. -->
3+
<selector xmlns:android="http://schemas.android.com/apk/res/android">
4+
<item android:alpha="0.12" android:color="@color/plugin_on_surface" android:state_enabled="false" />
5+
<item android:color="@color/plugin_secondary" />
6+
</selector>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Filled button label and icon: on-accent, and M3's 38% on-surface while disabled. -->
3+
<selector xmlns:android="http://schemas.android.com/apk/res/android">
4+
<item android:alpha="0.38" android:color="@color/plugin_on_surface" android:state_enabled="false" />
5+
<item android:color="@color/plugin_on_secondary" />
6+
</selector>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Filled button ripple, drawn in the label colour so it shows on the dark container. -->
3+
<selector xmlns:android="http://schemas.android.com/apk/res/android">
4+
<item android:alpha="0.12" android:color="@color/plugin_on_secondary" android:state_pressed="true" />
5+
<item android:alpha="0.12" android:color="@color/plugin_on_secondary" android:state_focused="true" />
6+
<item android:alpha="0.08" android:color="@color/plugin_on_secondary" android:state_hovered="true" />
7+
<item android:alpha="0.12" android:color="@color/plugin_on_secondary" />
8+
</selector>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Outlined button label and icon: on-surface, and M3's 38% while disabled. -->
3+
<selector xmlns:android="http://schemas.android.com/apk/res/android">
4+
<item android:alpha="0.38" android:color="@color/plugin_on_surface" android:state_enabled="false" />
5+
<item android:color="@color/plugin_on_surface" />
6+
</selector>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Outlined button ripple, drawn in the label colour over the transparent container. -->
3+
<selector xmlns:android="http://schemas.android.com/apk/res/android">
4+
<item android:alpha="0.12" android:color="@color/plugin_on_surface" android:state_pressed="true" />
5+
<item android:alpha="0.12" android:color="@color/plugin_on_surface" android:state_focused="true" />
6+
<item android:alpha="0.08" android:color="@color/plugin_on_surface" android:state_hovered="true" />
7+
<item android:alpha="0.12" android:color="@color/plugin_on_surface" />
8+
</selector>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- Outlined button border: the outline role, and M3's 12% on-surface while disabled. -->
3+
<selector xmlns:android="http://schemas.android.com/apk/res/android">
4+
<item android:alpha="0.12" android:color="@color/plugin_on_surface" android:state_enabled="false" />
5+
<item android:color="@color/plugin_outline" />
6+
</selector>

‎plugins/AI-Agent-Gemini/src/main/res/layout/fragment_gemini_settings.xml‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575

7676
<com.google.android.material.button.MaterialButton
7777
android:id="@+id/btn_clear_api_key"
78-
style="@style/PluginButton.Filled"
78+
style="@style/PluginButton.Outlined"
7979
android:layout_width="0dp"
8080
android:layout_height="wrap_content"
8181
android:layout_weight="1"
@@ -84,7 +84,7 @@
8484

8585
<com.google.android.material.button.MaterialButton
8686
android:id="@+id/btn_edit_api_key"
87-
style="@style/PluginButton.Filled"
87+
style="@style/PluginButton.Outlined"
8888
android:layout_width="0dp"
8989
android:layout_height="wrap_content"
9090
android:layout_marginStart="@dimen/space_sm"
@@ -104,12 +104,21 @@
104104

105105
<com.google.android.material.button.MaterialButton
106106
android:id="@+id/btn_get_free_key"
107-
style="@style/PluginButton.Filled"
107+
style="@style/PluginButton.Outlined"
108108
android:layout_width="match_parent"
109109
android:layout_height="wrap_content"
110110
android:layout_marginTop="@dimen/space_sm"
111111
android:text="@string/btn_get_free_key" />
112112

113+
<!-- Divider: ends the key section before the models. The Model header's top margin below
114+
mirrors this one's, so the line sits centred in the gap. Colour and thickness are set in
115+
code: app: attributes are dropped inside the host. -->
116+
<com.google.android.material.divider.MaterialDivider
117+
android:id="@+id/gemini_models_divider"
118+
android:layout_width="match_parent"
119+
android:layout_height="wrap_content"
120+
android:layout_marginTop="@dimen/space_xl" />
121+
113122
<!-- Model. One dropdown, the same control the OpenAI pane uses: the catalog is whatever the
114123
key can reach, so the list is the only way to change it. The chevron is not declared here —
115124
declared as app:endIconDrawable it draws blank inside the host, so
@@ -191,7 +200,7 @@
191200
<!-- One Refresh for both pickers: a single catalog walk fills both lists. -->
192201
<com.google.android.material.button.MaterialButton
193202
android:id="@+id/btn_refresh_models"
194-
style="@style/PluginButton.Filled"
203+
style="@style/PluginButton.Outlined"
195204
android:layout_width="match_parent"
196205
android:layout_height="wrap_content"
197206
android:layout_marginTop="@dimen/space_md"

0 commit comments

Comments
 (0)