fix:android-ui
Android / build-android (push) Successful in 2m30s

This commit is contained in:
intelligence committed 2026-08-29 16:08:08 +02:00
1 parent 066c7d758e
commit e4cf707d0a
14 files changed
+242 -52

No files matched your search

@@ -0,0 +1,50 @@
package io.github.transui.android;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import java.util.Arrays;
public final class AmplitudeView extends View {
private static final int LEVEL_COUNT = 22;
private final float[] levels = new float[LEVEL_COUNT];
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
public AmplitudeView(Context context, AttributeSet attributes) {
super(context, attributes);
paint.setColor(context.getColor(R.color.signal));
paint.setStrokeCap(Paint.Cap.ROUND);
}
void setAmplitude(float amplitude) {
System.arraycopy(levels, 1, levels, 0, levels.length - 1);
levels[levels.length - 1] = Math.max(0, Math.min(1, amplitude));
invalidate();
}
void reset() {
Arrays.fill(levels, 0);
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
float gap = availableWidth / (LEVEL_COUNT * 2 - 1);
float strokeWidth = Math.max(2, gap);
float centerY = getHeight() / 2f;
float maximumHeight = Math.max(0, getHeight() - getPaddingTop() - getPaddingBottom());
paint.setStrokeWidth(strokeWidth);
for (int index = 0; index < levels.length; index++) {
float height = Math.max(strokeWidth, levels[index] * maximumHeight);
float x = getPaddingLeft() + strokeWidth / 2 + index * gap * 2;
canvas.drawLine(x, centerY - height / 2, x, centerY + height / 2, paint);
}
}
}
@@ -3,9 +3,9 @@ package io.github.transui.android;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;
import android.widget.ImageButton;
public final class PushToTalkButton extends Button {
public final class PushToTalkButton extends ImageButton {
private Runnable pressedAction;
private Runnable releasedAction;
@@ -11,7 +11,6 @@ import android.os.Looper;
import android.os.SystemClock;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.widget.Button;
import android.widget.TextView;
import java.util.ArrayList;
@@ -32,6 +31,7 @@ public final class SpeechRecognitionActivity extends Activity {
private TextView status;
private TextView elapsed;
private AmplitudeView amplitude;
private PushToTalkButton record;
private AppConfig config;
private VoiceRecordingController controller;
@@ -46,6 +46,7 @@ public final class SpeechRecognitionActivity extends Activity {
setContentView(R.layout.recognition_panel);
status = findViewById(R.id.recognition_status);
elapsed = findViewById(R.id.recognition_elapsed);
amplitude = findViewById(R.id.recognition_amplitude);
record = findViewById(R.id.recognition_record);
findViewById(R.id.recognition_cancel).setOnClickListener(view -> cancelAndFinish());
@@ -77,11 +78,14 @@ public final class SpeechRecognitionActivity extends Activity {
}
private void configureRecordButton() {
record.setAlpha(1);
if (config.pushToTalk) {
record.setText(R.string.hold_to_talk);
record.setImageResource(R.drawable.ic_mic);
record.setContentDescription(getString(R.string.hold_to_talk));
record.setPushToTalkActions(this::startRecording, this::stopRecording);
} else {
record.setText(R.string.stop);
record.setImageResource(R.drawable.ic_stop);
record.setContentDescription(getString(R.string.stop));
record.setOnClickListener(view -> stopRecording());
}
}
@@ -112,13 +116,17 @@ public final class SpeechRecognitionActivity extends Activity {
private void stopRecording() {
if (!sessionActive || controller == null) return;
record.setEnabled(false);
record.setAlpha(0.4f);
amplitude.reset();
controller.stop();
}
private void showUnavailable(int message) {
status.setText(message);
elapsed.setText("");
amplitude.reset();
record.setEnabled(false);
record.setAlpha(0.4f);
}
private void cancelAndFinish() {
@@ -173,18 +181,26 @@ public final class SpeechRecognitionActivity extends Activity {
public void onRecording() {
runOnUiThread(() -> {
status.setText(R.string.listening);
record.setImageResource(R.drawable.ic_stop);
recordingStartedAt = SystemClock.elapsedRealtime();
elapsed.setText(R.string.elapsed_zero);
handler.post(updateElapsed);
});
}
@Override
public void onAmplitude(float level) {
runOnUiThread(() -> amplitude.setAmplitude(level));
}
@Override
public void onTranscribing() {
runOnUiThread(() -> {
handler.removeCallbacks(updateElapsed);
status.setText(R.string.transcribing);
amplitude.reset();
record.setEnabled(false);
record.setAlpha(0.4f);
});
}
@@ -9,7 +9,6 @@ import android.os.Looper;
import android.speech.SpeechRecognizer;
import android.view.View;
import android.view.inputmethod.InputConnection;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -17,6 +16,7 @@ public final class TransuiInputMethodService extends InputMethodService {
private final Handler handler = new Handler(Looper.getMainLooper());
private TextView status;
private AmplitudeView amplitude;
private PushToTalkButton record;
private AppConfig config;
private VoiceRecordingController controller;
@@ -27,6 +27,7 @@ public final class TransuiInputMethodService extends InputMethodService {
FrameLayout parent = new FrameLayout(this);
View view = getLayoutInflater().inflate(R.layout.voice_input, parent, false);
status = view.findViewById(R.id.voice_status);
amplitude = view.findViewById(R.id.voice_amplitude);
record = view.findViewById(R.id.voice_record);
view.findViewById(R.id.voice_keyboard).setOnClickListener(button -> returnToKeyboard());
return view;
@@ -63,13 +64,16 @@ public final class TransuiInputMethodService extends InputMethodService {
private void configureRecordButton() {
record.setEnabled(true);
record.setAlpha(1);
record.setOnClickListener(null);
record.setPushToTalkActions(null, null);
if (config.pushToTalk) {
record.setText(R.string.hold_to_talk);
record.setImageResource(R.drawable.ic_mic);
record.setContentDescription(getString(R.string.hold_to_talk));
record.setPushToTalkActions(this::startRecording, this::stopRecording);
} else {
record.setText(R.string.stop);
record.setImageResource(R.drawable.ic_stop);
record.setContentDescription(getString(R.string.stop));
record.setOnClickListener(view -> stopRecording());
}
}
@@ -84,18 +88,24 @@ public final class TransuiInputMethodService extends InputMethodService {
private void stopRecording() {
if (!sessionActive || controller == null) return;
record.setEnabled(false);
record.setAlpha(0.4f);
amplitude.reset();
controller.stop();
}
private void cancelSession() {
handler.removeCallbacksAndMessages(null);
if (controller != null) controller.cancel();
controller = null;
sessionActive = false;
if (amplitude != null) amplitude.reset();
}
private void showUnavailable(int message) {
status.setText(message);
amplitude.reset();
record.setEnabled(false);
record.setAlpha(0.4f);
}
private void returnToKeyboard() {
@@ -110,14 +120,24 @@ public final class TransuiInputMethodService extends InputMethodService {
private final class ImeListener implements VoiceRecordingController.Listener {
@Override
public void onRecording() {
handler.post(() -> status.setText(R.string.listening));
handler.post(() -> {
status.setText(R.string.listening);
record.setImageResource(R.drawable.ic_stop);
});
}
@Override
public void onAmplitude(float level) {
handler.post(() -> amplitude.setAmplitude(level));
}
@Override
public void onTranscribing() {
handler.post(() -> {
status.setText(R.string.transcribing);
amplitude.reset();
record.setEnabled(false);
record.setAlpha(0.4f);
});
}
@@ -19,6 +19,8 @@ final class VoiceRecordingController {
interface Listener {
void onRecording();
void onAmplitude(float amplitude);
void onTranscribing();
void onResult(String text);
@@ -126,6 +128,7 @@ final class VoiceRecordingController {
return null;
}
appendSamples(pcm, samples, count);
listener.onAmplitude(calculateAmplitude(samples, count));
if (stopRequested.get()) break;
if (SystemClock.elapsedRealtime() - startedAt >= MAX_RECORDING_MS) break;
}
@@ -155,4 +158,16 @@ final class VoiceRecordingController {
output.write((sample >>> 8) & 0xff);
}
}
private static float calculateAmplitude(short[] samples, int count) {
double squaredTotal = 0;
for (int index = 0; index < count; index++) {
double normalized = samples[index] / (double) Short.MAX_VALUE;
squaredTotal += normalized * normalized;
}
double rootMeanSquare = Math.sqrt(squaredTotal / count);
if (rootMeanSquare <= 0) return 0;
double decibels = 20 * Math.log10(rootMeanSquare);
return (float) Math.max(0, Math.min(1, (decibels + 50) / 50));
}
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M20,5H4C2.89,5 2,5.89 2,7v10c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V7c0,-1.11 -0.89,-2 -2,-2zM11,8h2v2h-2V8zM11,11h2v2h-2v-2zM8,8h2v2H8V8zM8,11h2v2H8v-2zM7,13H5v-2h2v2zM7,10H5V8h2v2zM17,17H7v-2h10v2zM17,13h-3v-2h3v2zM17,10h-3V8h3v2zM19,13h-1v-2h1v2zM19,10h-1V8h1v2z" />
</vector>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,14c1.66,0 3,-1.34 3,-3V5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM17,11c0,2.76 -2.24,5 -5,5s-5,-2.24 -5,-5H5c0,3.53 2.61,6.43 6,6.92V21h2v-3.08c3.39,-0.49 6,-3.39 6,-6.92h-2z" />
</vector>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#FF000000" android:pathData="M6,6h12v12H6z" />
</vector>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/ripple_on_signal">
<item>
<shape android:shape="oval">
<solid android:color="@color/signal" />
</shape>
</item>
</ripple>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/ripple_signal">
<item>
<shape android:shape="oval">
<solid android:color="@color/mist" />
</shape>
</item>
</ripple>
@@ -41,12 +41,28 @@
android:textColor="@color/slate"
android:textSize="14sp" />
<io.github.transui.android.AmplitudeView
android:id="@+id/recognition_amplitude"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginTop="12dp"
android:importantForAccessibility="no"
android:paddingStart="12dp"
android:paddingTop="10dp"
android:paddingEnd="12dp"
android:paddingBottom="10dp" />
<io.github.transui.android.PushToTalkButton
android:id="@+id/recognition_record"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="22dp"
android:text="@string/stop" />
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="14dp"
android:background="@drawable/voice_primary_button"
android:contentDescription="@string/stop"
android:padding="16dp"
android:src="@drawable/ic_stop"
android:tint="@color/paper" />
<Button
android:id="@+id/recognition_cancel"
+62 -39
View File
@@ -1,59 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/paper"
android:orientation="vertical"
android:paddingStart="20dp"
android:paddingTop="18dp"
android:paddingEnd="20dp"
android:paddingBottom="18dp">
tools:ignore="Overdraw">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/line" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
android:orientation="horizontal"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp">
<View
android:layout_width="5dp"
android:layout_height="40dp"
android:background="@color/signal" />
<View
android:layout_width="4dp"
android:layout_height="28dp"
android:background="@color/signal" />
<TextView
android:id="@+id/voice_status"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="14dp"
android:paddingEnd="14dp"
android:text="@string/listening"
android:textColor="@color/ink"
android:textSize="17sp"
android:textStyle="bold" />
<TextView
android:id="@+id/voice_status"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:text="@string/listening"
android:textColor="@color/ink"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:orientation="horizontal">
android:layout_height="64dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="14dp"
android:paddingEnd="14dp"
android:paddingBottom="8dp">
<Button
android:id="@+id/voice_keyboard"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/keyboard" />
<ImageButton
android:id="@+id/voice_keyboard"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/voice_secondary_button"
android:contentDescription="@string/keyboard"
android:padding="12dp"
android:src="@drawable/ic_keyboard"
android:tint="@color/ink" />
<io.github.transui.android.PushToTalkButton
android:id="@+id/voice_record"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:text="@string/stop" />
<io.github.transui.android.AmplitudeView
android:id="@+id/voice_amplitude"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:importantForAccessibility="no"
android:paddingTop="8dp"
android:paddingBottom="8dp" />
<io.github.transui.android.PushToTalkButton
android:id="@+id/voice_record"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/voice_primary_button"
android:contentDescription="@string/stop"
android:padding="12dp"
android:src="@drawable/ic_stop"
android:tint="@color/paper" />
</LinearLayout>
</LinearLayout>
@@ -6,4 +6,6 @@
<color name="mist">#11191D</color>
<color name="paper">#192329</color>
<color name="line">#34444B</color>
<color name="ripple_on_signal">#3DFFFFFF</color>
<color name="ripple_signal">#3342C9C1</color>
</resources>
@@ -6,4 +6,6 @@
<color name="mist">#F2F6F7</color>
<color name="paper">#FFFFFF</color>
<color name="line">#D5E0E3</color>
<color name="ripple_on_signal">#3DFFFFFF</color>
<color name="ripple_signal">#33008D87</color>
</resources>