Skip to content

Commit 1a10999

Browse files
committed
fix(ai-agents): draw the dropdown chevron and unify both panes [ADFA-6119]
Material 1.10 ignores app:endIconDrawable, so the chevron is set in code on the OpenAI preset and model fields. Gemini's Spinner becomes the same control and its pane moves onto the shared button, text and spacing styles.
1 parent 2480ff9 commit 1a10999

11 files changed

Lines changed: 460 additions & 180 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ class GeminiPlugin : IPlugin, DocumentationExtension {
277277
does by embedding every chunk of the project with this model. It
278278
is a separate setting because no Gemini model does both: this list
279279
holds the models that advertise <code>embedContent</code>, and the
280-
<b>Gemini Model</b> list above holds those that advertise
280+
<b>Model</b> list above holds those that advertise
281281
<code>generateContent</code>.</p>
282282
<p>Changing it changes the vector space, so the project is indexed
283283
again from scratch. Vectors from two different models are not

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

Lines changed: 165 additions & 150 deletions
Large diffs are not rendered by default.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="24dp"
3+
android:height="24dp"
4+
android:viewportWidth="24"
5+
android:viewportHeight="24"
6+
android:tint="?attr/colorControlNormal">
7+
<path
8+
android:fillColor="@android:color/white"
9+
android:pathData="M7,10l5,5 5,-5z" />
10+
</vector>

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

Lines changed: 118 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Material fields and buttons throughout, on the shared house spacing scale, so this pane and the
4+
OpenAI one read as the same screen with a different backend behind it. The root android:theme is
5+
what guarantees the plugin's own button/input styles win over whatever theme the host builds the
6+
inflation Context from.
7+
-->
28
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
9+
xmlns:app="http://schemas.android.com/apk/res-auto"
310
android:layout_width="match_parent"
411
android:layout_height="wrap_content"
5-
android:orientation="vertical">
12+
android:orientation="vertical"
13+
android:theme="@style/ThemeOverlay.Plugin">
14+
15+
<!-- API key. Collapses to the status line once a key is stored. -->
616

717
<TextView
818
android:id="@+id/gemini_api_key_status_text"
919
android:layout_width="match_parent"
1020
android:layout_height="wrap_content"
11-
android:layout_marginBottom="8dp"
12-
android:textAppearance="?android:attr/textAppearanceMedium"
13-
android:textColor="?android:attr/textColorSecondary"
21+
android:layout_marginBottom="@dimen/space_sm"
22+
android:textAppearance="@style/Plugin.Text.Body"
1423
android:visibility="gone" />
1524

1625
<LinearLayout
@@ -20,11 +29,11 @@
2029
android:orientation="vertical">
2130

2231
<TextView
32+
android:id="@+id/gemini_api_key_label"
33+
style="@style/Plugin.SectionHeader"
2334
android:layout_width="match_parent"
2435
android:layout_height="wrap_content"
25-
android:text="@string/label_gemini_api_key"
26-
android:textAppearance="?android:attr/textAppearanceSmall"
27-
android:paddingBottom="4dp"/>
36+
android:text="@string/label_gemini_api_key" />
2837

2938
<!--
3039
The reveal toggle is the box's own end icon rather than the loose ImageButton that used to
@@ -53,52 +62,142 @@
5362

5463
<TextView
5564
android:id="@+id/gemini_key_verification_text"
65+
style="@style/Plugin.StatusLine"
5666
android:layout_width="match_parent"
5767
android:layout_height="wrap_content"
58-
android:layout_marginTop="8dp"
59-
android:drawablePadding="6dp"
60-
android:gravity="center_vertical"
61-
android:textAppearance="?android:attr/textAppearanceSmall"
62-
android:textColor="?android:attr/textColorSecondary"
68+
android:layout_marginTop="@dimen/space_sm"
6369
android:visibility="gone" />
6470

6571
<LinearLayout
6672
android:layout_width="match_parent"
6773
android:layout_height="wrap_content"
68-
android:layout_marginTop="8dp"
74+
android:layout_marginTop="@dimen/space_md"
6975
android:orientation="horizontal">
7076

71-
<Button
77+
<com.google.android.material.button.MaterialButton
7278
android:id="@+id/btn_clear_api_key"
79+
style="@style/PluginButton.Filled"
7380
android:layout_width="0dp"
7481
android:layout_height="wrap_content"
7582
android:layout_weight="1"
7683
android:text="@string/btn_clear_api_key"
7784
android:visibility="gone" />
7885

79-
<Button
86+
<com.google.android.material.button.MaterialButton
8087
android:id="@+id/btn_edit_api_key"
88+
style="@style/PluginButton.Filled"
8189
android:layout_width="0dp"
8290
android:layout_height="wrap_content"
83-
android:layout_marginStart="8dp"
91+
android:layout_marginStart="@dimen/space_sm"
8492
android:layout_weight="1"
8593
android:text="@string/btn_edit_api_key"
8694
android:visibility="gone" />
8795

88-
<Button
96+
<com.google.android.material.button.MaterialButton
8997
android:id="@+id/btn_save_api_key"
98+
style="@style/PluginButton.Filled"
9099
android:layout_width="0dp"
91100
android:layout_height="wrap_content"
92101
android:layout_weight="1"
93102
android:text="@string/btn_save_api_key" />
94103

95104
</LinearLayout>
96105

97-
<Button
106+
<com.google.android.material.button.MaterialButton
98107
android:id="@+id/btn_get_free_key"
108+
style="@style/PluginButton.Filled"
99109
android:layout_width="match_parent"
100110
android:layout_height="wrap_content"
101-
android:layout_marginTop="4dp"
111+
android:layout_marginTop="@dimen/space_sm"
102112
android:text="@string/btn_get_free_key" />
103113

114+
<!-- Model. One dropdown, the same control the OpenAI pane uses: the catalog is whatever the
115+
key can reach, so the list is the only way to change it. The chevron is not declared here —
116+
Material 1.10 never reads app:endIconDrawable, so the icon a layout asks for draws blank;
117+
setupDropdownEndIcon() sets it on every box in code. -->
118+
119+
<TextView
120+
android:id="@+id/gemini_model_label"
121+
style="@style/Plugin.SectionHeader"
122+
android:layout_width="match_parent"
123+
android:layout_height="wrap_content"
124+
android:layout_marginTop="@dimen/space_xl"
125+
android:text="@string/label_gemini_model" />
126+
127+
<com.google.android.material.textfield.TextInputLayout
128+
android:id="@+id/gemini_model_box"
129+
style="@style/PluginTextInputLayout"
130+
android:layout_width="match_parent"
131+
android:layout_height="wrap_content"
132+
android:hint="@string/hint_gemini_model"
133+
app:endIconContentDescription="@string/cd_show_models"
134+
app:endIconMode="custom">
135+
136+
<AutoCompleteTextView
137+
android:id="@+id/gemini_model_input"
138+
style="@style/PluginTextInputEditText"
139+
android:layout_width="match_parent"
140+
android:layout_height="wrap_content"
141+
android:completionThreshold="0"
142+
android:inputType="none"
143+
android:maxLines="1"
144+
android:popupBackground="@color/plugin_surface" />
145+
</com.google.android.material.textfield.TextInputLayout>
146+
147+
<TextView
148+
android:id="@+id/gemini_model_hint_text"
149+
android:layout_width="match_parent"
150+
android:layout_height="wrap_content"
151+
android:layout_marginTop="@dimen/space_xs"
152+
android:text="@string/hint_gemini_model_help"
153+
android:textAppearance="@style/Plugin.Text.Small" />
154+
155+
<!-- Embedding model. The other half of the same catalog: semantic search embeds with this one,
156+
never with the chat model above. -->
157+
158+
<TextView
159+
android:id="@+id/gemini_embedding_model_label"
160+
style="@style/Plugin.SectionHeader"
161+
android:layout_width="match_parent"
162+
android:layout_height="wrap_content"
163+
android:layout_marginTop="@dimen/space_xl"
164+
android:text="@string/label_gemini_embedding_model" />
165+
166+
<com.google.android.material.textfield.TextInputLayout
167+
android:id="@+id/gemini_embedding_model_box"
168+
style="@style/PluginTextInputLayout"
169+
android:layout_width="match_parent"
170+
android:layout_height="wrap_content"
171+
android:hint="@string/hint_gemini_embedding_model"
172+
app:endIconContentDescription="@string/cd_show_embedding_models"
173+
app:endIconMode="custom">
174+
175+
<AutoCompleteTextView
176+
android:id="@+id/gemini_embedding_model_input"
177+
style="@style/PluginTextInputEditText"
178+
android:layout_width="match_parent"
179+
android:layout_height="wrap_content"
180+
android:completionThreshold="0"
181+
android:inputType="none"
182+
android:maxLines="1"
183+
android:popupBackground="@color/plugin_surface" />
184+
</com.google.android.material.textfield.TextInputLayout>
185+
186+
<TextView
187+
android:id="@+id/gemini_embedding_model_hint_text"
188+
android:layout_width="match_parent"
189+
android:layout_height="wrap_content"
190+
android:layout_marginTop="@dimen/space_xs"
191+
android:text="@string/hint_gemini_embedding_model_help"
192+
android:textAppearance="@style/Plugin.Text.Small" />
193+
194+
<!-- One Refresh for both pickers: a single catalog walk fills both lists. -->
195+
<com.google.android.material.button.MaterialButton
196+
android:id="@+id/btn_refresh_models"
197+
style="@style/PluginButton.Filled"
198+
android:layout_width="match_parent"
199+
android:layout_height="wrap_content"
200+
android:layout_marginTop="@dimen/space_md"
201+
android:text="@string/refresh_models" />
202+
104203
</LinearLayout>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
One row of the model dropdown. The plugin's own item layout rather than
4+
android.R.layout.simple_list_item_1, whose text color comes from the framework theme in effect
5+
when the popup window is built — which is not reliably the one the pane is drawn in.
6+
-->
7+
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
8+
android:layout_width="match_parent"
9+
android:layout_height="wrap_content"
10+
android:background="?android:attr/selectableItemBackground"
11+
android:gravity="center_vertical"
12+
android:minHeight="@dimen/touch_target_min"
13+
android:paddingHorizontal="@dimen/space_lg"
14+
android:paddingVertical="@dimen/space_md"
15+
android:textAppearance="@style/Plugin.Text.Body" />
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Spacing, radius and type scale shared with the other CoGo house plugins (see
4+
client-time-tracker), so this pane sits on the same grid as the rest of the IDE's plugin UI
5+
instead of the ad-hoc 4/8/24dp margins it used before.
6+
-->
7+
<resources>
8+
<dimen name="space_xs">4dp</dimen>
9+
<dimen name="space_sm">8dp</dimen>
10+
<dimen name="space_md">12dp</dimen>
11+
<dimen name="space_lg">16dp</dimen>
12+
<dimen name="space_xl">24dp</dimen>
13+
14+
<dimen name="radius_md">8dp</dimen>
15+
<dimen name="radius_lg">12dp</dimen>
16+
17+
<dimen name="text_micro">11sp</dimen>
18+
<dimen name="text_small">13sp</dimen>
19+
<dimen name="text_body">15sp</dimen>
20+
21+
<dimen name="touch_target_min">48dp</dimen>
22+
</resources>

‎plugins/AI-Agent-Gemini/src/main/res/values/strings.xml‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<string name="gemini_error_failed_reason">The Gemini request failed. %1$s</string>
1717

1818
<!-- Settings pane this backend contributes (see GeminiSettingsFragment) -->
19-
<string name="label_gemini_api_key">Gemini API Key</string>
19+
<string name="label_gemini_api_key">API Key</string>
2020
<string name="hint_gemini_api_key">Enter your Gemini API key</string>
2121
<!-- Reveal toggle on the masked credential field. Deliberately the same wording in
2222
every AI plugin: one control, one announcement, whatever the field holds. -->
@@ -51,9 +51,16 @@
5151
<string name="msg_key_hint_edit_first">Copied a new key? Tap Edit, then paste it into the field.</string>
5252
<string name="msg_no_browser_for_key">No browser available. The link was copied — open it on another device: %s</string>
5353
<string name="msg_key_link_copy_failed">Couldn\'t open a browser. Get your key at %s</string>
54-
<string name="label_gemini_model">Gemini Model</string>
55-
<string name="label_gemini_embedding_model">Gemini Embedding Model</string>
56-
<string name="current_model">Current: %s</string>
54+
<string name="label_gemini_model">Model</string>
55+
<string name="hint_gemini_model">Gemini model</string>
56+
<string name="cd_show_models">Show the model list</string>
57+
<string name="hint_gemini_model_help">Refresh to list the models your key can reach.</string>
58+
<string name="hint_gemini_model_live">Tap to pick from the models this key can reach.</string>
59+
<string name="label_gemini_embedding_model">Embedding Model</string>
60+
<string name="hint_gemini_embedding_model">Gemini embedding model</string>
61+
<string name="cd_show_embedding_models">Show the embedding model list</string>
62+
<string name="hint_gemini_embedding_model_help">Used by semantic search, never for chat. Refresh to list the embedding models your key can reach.</string>
63+
<string name="hint_gemini_embedding_model_live">Tap to pick from the embedding models this key can reach.</string>
5764
<string name="refresh_models">Refresh Models</string>
5865
<string name="loading">Loading…</string>
5966
<string name="model_changed">Model changed to %s</string>

‎plugins/AI-Agent-Gemini/src/main/res/values/styles.xml‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,85 @@
3333
<item name="colorOnError">@color/plugin_on_error</item>
3434
<item name="colorErrorContainer">@color/plugin_error_container</item>
3535
<item name="colorOnErrorContainer">@color/plugin_on_error_container</item>
36+
37+
<item name="materialButtonStyle">@style/PluginButton.Filled</item>
38+
<item name="textInputStyle">@style/PluginTextInputLayout</item>
39+
</style>
40+
41+
<!--
42+
ThemeOverlay applied via android:theme on the settings pane's root. The host may build the
43+
plugin's inflation Context by layering PluginTheme onto the *activity's* theme without
44+
forcing it (PluginResourceContext), in which case PluginTheme's own items lose and the pane
45+
falls back to framework widget defaults. android:theme on a ViewGroup wraps that subtree's
46+
inflation Context, which is where MaterialButton and TextInputLayout look their styles up, so
47+
this is what guarantees the pane is styled either way. No colors here: the widget styles pin
48+
their own, so nothing depends on which theme won.
49+
-->
50+
<style name="ThemeOverlay.Plugin" parent="">
51+
<item name="materialButtonStyle">@style/PluginButton.Filled</item>
52+
<item name="textInputStyle">@style/PluginTextInputLayout</item>
53+
</style>
54+
55+
<!-- Typography -->
56+
57+
<style name="Plugin.Text.Body" parent="TextAppearance.Material3.BodyMedium">
58+
<item name="android:textColor">@color/plugin_on_surface</item>
59+
<item name="android:textSize">@dimen/text_body</item>
60+
</style>
61+
62+
<style name="Plugin.Text.Small" parent="TextAppearance.Material3.BodySmall">
63+
<item name="android:textColor">@color/plugin_text_muted</item>
64+
<item name="android:textSize">@dimen/text_small</item>
65+
<item name="android:lineSpacingMultiplier">1.25</item>
3666
</style>
3767

68+
<!-- Eyebrow label above a field, as used by the IDE's own grouped settings. -->
69+
<style name="Plugin.Text.Label" parent="TextAppearance.Material3.LabelMedium">
70+
<item name="android:textColor">@color/plugin_text_muted</item>
71+
<item name="android:textSize">@dimen/text_micro</item>
72+
<item name="android:textAllCaps">true</item>
73+
<item name="android:letterSpacing">0.08</item>
74+
<item name="android:fontFamily">sans-serif-medium</item>
75+
</style>
76+
77+
<!-- Explicit parent="" required: dot-name inference would look up @style/Plugin, which does
78+
not exist, and break resource linking. -->
79+
<style name="Plugin.SectionHeader" parent="">
80+
<item name="android:textAppearance">@style/Plugin.Text.Label</item>
81+
<item name="android:paddingBottom">@dimen/space_xs</item>
82+
</style>
83+
84+
<!-- A status line under a field: one icon, one sentence, muted. -->
85+
<style name="Plugin.StatusLine" parent="">
86+
<item name="android:textAppearance">@style/Plugin.Text.Small</item>
87+
<item name="android:drawablePadding">@dimen/space_sm</item>
88+
<item name="android:gravity">center_vertical</item>
89+
</style>
90+
91+
<!-- Buttons. Every color is a direct @color/* reference, never ?attr/*: the host may build the
92+
plugin's inflation theme by layering PluginTheme onto the activity's own without forcing
93+
it, and a Material 3 widget whose color roles resolve against that theme comes out as an
94+
unreadable slab — dark-on-dark for an outlined button in light mode. The palette is neutral
95+
by choice, matching the IDE's own grey accent rather than this plugin's orange, on a screen
96+
whose chrome belongs to AI Core. Light/dark comes from values-night/colors.xml.
97+
98+
One style for every button on the pane: a secondary action that styled itself differently
99+
is exactly what broke on the OpenAI pane, and the hierarchy is not worth a second way to
100+
get contrast wrong. -->
101+
<style name="PluginButton.Filled" parent="Widget.Material3.Button">
102+
<item name="android:textAllCaps">false</item>
103+
<item name="android:fontFamily">sans-serif-medium</item>
104+
<item name="android:letterSpacing">0</item>
105+
<item name="cornerRadius">@dimen/radius_md</item>
106+
<item name="android:minHeight">@dimen/touch_target_min</item>
107+
<item name="android:textColor">@color/plugin_on_secondary</item>
108+
<item name="backgroundTint">@color/plugin_secondary</item>
109+
<item name="iconTint">@color/plugin_on_secondary</item>
110+
<item name="rippleColor">@color/plugin_ripple</item>
111+
</style>
112+
113+
<!-- Inputs -->
114+
38115
<!--
39116
The API key box, styled by an explicit style= on the widget rather than through the theme's
40117
textInputStyle: the host may build this plugin's inflation Context by layering PluginTheme
@@ -47,9 +124,17 @@
47124
<item name="hintTextColor">@color/plugin_text_muted</item>
48125
<item name="android:textColorHint">@color/plugin_text_muted</item>
49126
<item name="endIconTint">@color/plugin_on_surface_variant</item>
127+
<item name="boxCornerRadiusTopStart">@dimen/radius_md</item>
128+
<item name="boxCornerRadiusTopEnd">@dimen/radius_md</item>
129+
<item name="boxCornerRadiusBottomStart">@dimen/radius_md</item>
130+
<item name="boxCornerRadiusBottomEnd">@dimen/radius_md</item>
50131
</style>
51132

133+
<!-- The text inside every box. Applied explicitly because the model field is an
134+
AutoCompleteTextView rather than a TextInputEditText, and so does not pick the box's own
135+
text style up from the theme. -->
52136
<style name="PluginTextInputEditText" parent="Widget.Material3.TextInputEditText.OutlinedBox">
137+
<item name="android:textAppearance">@style/Plugin.Text.Body</item>
53138
<item name="android:textColor">@color/plugin_on_surface</item>
54139
</style>
55140

0 commit comments

Comments
 (0)