Ich habe einfach versucht, die API über die benutzerdefinierte Tastatur aufzurufen, z. B. wenn Text ausgewählt ist, und auf eine beliebige Schaltfläche zu tippen. Überprüfen Sie die Grammatik, dann sollte die API gemäß meinem Code aufgerufen werden und das Ergebnis auf der Tastatur anzeigen, wo Buchstaben angezeigt werden. Wenn die API aufgerufen wird, wird eine Antwort angezeigt. Es wird unendlich geladen, es werden keine Protokolle gedruckt und nichts angezeigt. Ich weiß nicht einmal, was genau das ist Problem
RewriterkeyboardService.kt
Code: Select all
private const val CHANNEL = "keyboard_api"
private const val TAG = "KeyboardService"
class RewriterKeyboardService : InputMethodService() {
private lateinit var methodChannel: MethodChannel
private var inputConnection: InputConnection? = null
private lateinit var flutterEngine: FlutterEngine
private lateinit var keyboardView: View
private var isFlutterReady = false
private val pendingCalls = mutableListOf()
private var isShiftOn = false
private var isCapsLockOn = false
private val letterKeys = listOf(
"Q","W","E","R","T","Y","U","I","O","P",
"A","S","D","F","G","H","J","K","L",
"Z","X","C","V","B","N","M"
)
// ================= LIFECYCLE =================
override fun onStartInputView(info: EditorInfo?, restarting: Boolean) {
super.onStartInputView(info, restarting)
Log.d(TAG, "onStartInputView")
inputConnection = currentInputConnection
}
override fun onCreateInputView(): View {
Log.d(TAG, "onCreateInputView")
keyboardView = layoutInflater.inflate(R.layout.keyboard_view, null)
// Initialize Flutter engine first
initializeFlutterEngine()
setupKeyboard(keyboardView)
setupActionButtons(keyboardView)
setupResultButtons()
showKeyboardState()
return keyboardView
}
private fun showLoadingState() {
Log.d(TAG, "Showing loading state")
runOnUiThread {
keyboardView.findViewById(R.id.loading_container)?.visibility = View.VISIBLE
keyboardView.findViewById(R.id.layout_letters)?.visibility = View.GONE
keyboardView.findViewById(R.id.result_container)?.visibility = View.GONE
}
}
private fun showKeyboardState() {
Log.d(TAG, "Showing keyboard state")
runOnUiThread {
keyboardView.findViewById(R.id.loading_container)?.visibility = View.GONE
keyboardView.findViewById(R.id.layout_letters)?.visibility = View.VISIBLE
keyboardView.findViewById(R.id.result_container)?.visibility = View.GONE
}
}
private fun showResultState(text: String) {
Log.d(TAG, "Showing result state: ${text.take(50)}...")
runOnUiThread {
keyboardView.findViewById(R.id.result_text)?.text = text
keyboardView.findViewById(R.id.loading_container)?.visibility = View.GONE
keyboardView.findViewById(R.id.layout_letters)?.visibility = View.GONE
keyboardView.findViewById(R.id.result_container)?.visibility = View.VISIBLE
}
}
private fun ensureInputConnection(): Boolean {
if (inputConnection == null) {
inputConnection = currentInputConnection
}
return inputConnection != null
}
private fun setupKeyboard(view: View) {
// Setup letter keys
letterKeys.forEach { key ->
val id = resources.getIdentifier("key_${key.lowercase()}", "id", packageName)
val button = view.findViewById(id) ?: return@forEach
attachKeyTouch(button) {
if (!ensureInputConnection()) return@attachKeyTouch
val text = if (isShiftOn || isCapsLockOn) key else key.lowercase()
inputConnection?.commitText(text, 1)
if (isShiftOn && !isCapsLockOn) {
isShiftOn = false
updateKeyLabels(view)
}
}
}
// Setup number keys (0-9)
for (i in 0..9) {
view.findViewById(resources.getIdentifier("key_$i", "id", packageName))?.let { button ->
attachKeyTouch(button) {
if (!ensureInputConnection()) return@attachKeyTouch
inputConnection?.commitText(i.toString(), 1)
}
}
}
// Setup special keys
view.findViewById(R.id.key_space)?.let {
attachKeyTouch(it) {
if (!ensureInputConnection()) return@attachKeyTouch
inputConnection?.commitText(" ", 1)
}
}
view.findViewById(R.id.key_backspace)?.let {
attachKeyTouch(it) {
if (!ensureInputConnection()) return@attachKeyTouch
inputConnection?.deleteSurroundingText(1, 0)
}
}
view.findViewById(R.id.key_enter)?.let {
attachKeyTouch(it) {
if (!ensureInputConnection()) return@attachKeyTouch
inputConnection?.sendKeyEvent(
KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)
)
}
}
view.findViewById(R.id.key_shift)?.let {
attachKeyTouch(it) {
isShiftOn = !isShiftOn
updateKeyLabels(view)
}
}
view.findViewById(R.id.key_symbols)?.let {
attachKeyTouch(it) {
if (!ensureInputConnection()) return@attachKeyTouch
inputConnection?.commitText(",", 1)
}
}
updateKeyLabels(view)
}
// ================= ACTION BUTTONS =================
private fun setupActionButtons(root: View) {
fun callFlutter(feature: String) {
Log.d(TAG, "callFlutter called with feature: $feature")
if (!ensureInputConnection()) {
Log.e(TAG, "No input connection")
return
}
val selectedText = inputConnection?.getSelectedText(0)?.toString()
Log.d(TAG, "Selected text: $selectedText")
val extractedText = inputConnection?.getExtractedText(ExtractedTextRequest(), 0)?.text?.toString()
Log.d(TAG, "Extracted text: ${extractedText?.take(50)}...")
val text = selectedText ?: extractedText ?: run {
Log.e(TAG, "No text available")
return
}
Log.d(TAG, "Calling API with text length: ${text.length}")
showLoadingState()
if (!isFlutterReady) {
Log.e(TAG, "Flutter not ready — queueing call")
pendingCalls.add(Pair(text, feature))
return
}
callFlutterInternal(text, feature)
}
root.findViewById(R.id.btn_check_grammar)?.setOnClickListener {
Log.d(TAG, "Check grammar clicked")
callFlutter("fix-grammar")
}
root.findViewById(R.id.btn_rephrase)?.setOnClickListener {
Log.d(TAG, "Rephrase clicked")
callFlutter("change-tone")
}
root.findViewById(R.id.btn_summarize)?.setOnClickListener {
Log.d(TAG, "Summarize clicked")
callFlutter("summarise")
}
root.findViewById(R.id.btn_ai_reply)?.setOnClickListener {
Log.d(TAG, "AI Reply clicked")
callFlutter("ai-reply")
}
root.findViewById(R.id.btn_email_gen)?.setOnClickListener {
Log.d(TAG, "Email Gen clicked")
callFlutter("email-generator")
}
root.findViewById(R.id.btn_prompt_gen)?.setOnClickListener {
Log.d(TAG, "Prompt Gen clicked")
callFlutter("prompt-generator")
}
}
// ================= RESULT =================
private fun setupResultButtons() {
keyboardView.findViewById(R.id.btn_apply)?.setOnClickListener {
if (!ensureInputConnection()) return@setOnClickListener
val result = keyboardView.findViewById(R.id.result_text).text.toString()
inputConnection?.commitText(result, 1)
showKeyboardState()
}
keyboardView.findViewById(R.id.btn_close)?.setOnClickListener {
showKeyboardState()
}
}
// ================= TOUCH =================
private fun attachKeyTouch(view: View, onPress: () -> Unit) {
view.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
view.isPressed = true
onPress()
true
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
view.isPressed = false
true
}
else -> false
}
}
}
private fun updateKeyLabels(view: View) {
letterKeys.forEach { letter ->
val id = resources.getIdentifier("key_${letter.lowercase()}", "id", packageName)
view.findViewById(id)?.text =
if (isShiftOn || isCapsLockOn) letter else letter.lowercase()
}
}
private fun initializeFlutterEngine() {
Log.d(TAG, "Initializing Flutter engine")
val flutterLoader = FlutterInjector.instance().flutterLoader()
if (!flutterLoader.initialized()) {
flutterLoader.startInitialization(applicationContext)
flutterLoader.ensureInitializationComplete(applicationContext, null)
}
flutterEngine = FlutterEngine(applicationContext)
// ✅ REQUIRED for InputMethodService
GeneratedPluginRegistrant.registerWith(flutterEngine)
// ✅ ASSIGN to class field (not local)
methodChannel = MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
)
methodChannel.setMethodCallHandler { call, result ->
when (call.method) {
"flutterReady" -> {
Log.d(TAG, "Flutter READY received")
isFlutterReady = true
pendingCalls.forEach {
callFlutterInternal(it.first, it.second)
}
pendingCalls.clear()
result.success(null)
}
else -> result.notImplemented()
}
}
val entrypoint = DartExecutor.DartEntrypoint(
flutterLoader.findAppBundlePath(),
"keyboardToolbarMain"
)
flutterEngine.dartExecutor.executeDartEntrypoint(entrypoint)
val flutterView = FlutterView(this)
flutterView.layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
(52 * resources.displayMetrics.density).toInt()
)
flutterView.attachToFlutterEngine(flutterEngine)
keyboardView
.findViewById(R.id.flutter_toolbar_container)
?.addView(flutterView)
}
private fun callFlutterInternal(text: String, feature: String) {
Log.d(TAG, "callFlutterInternal: $feature, text length: ${text.length}")
Handler(Looper.getMainLooper()).post {
try {
methodChannel.invokeMethod(
"callApi",
mapOf(
"text" to text,
"feature" to feature
),
object : MethodChannel.Result {
override fun success(result: Any?) {
Log.d(
TAG,
"API call succeeded, result type: ${result?.javaClass?.simpleName}"
)
val resultText = result as? String ?: "No result received"
Log.d(TAG, "Result text: ${resultText.take(100)}...")
showResultState(resultText)
}
override fun error(
code: String,
message: String?,
details: Any?
) {
Log.e(TAG, "API error: $code - $message")
showResultState("Error: $message")
}
override fun notImplemented() {
Log.e(TAG, "Method not implemented")
showResultState("Feature not available")
}
}
)
} catch (e: Exception) {
Log.e(TAG, "Exception calling Flutter", e)
showResultState("Error: ${e.message}")
}
}
}
private fun runOnUiThread(action: () -> Unit) {
Handler(Looper.getMainLooper()).post(action)
}
override fun onEvaluateFullscreenMode(): Boolean = false
override fun onDestroy() {
Log.d(TAG, "onDestroy")
flutterEngine.destroy()
super.onDestroy()
}
}
Code: Select all
Code: Select all
@pragma('vm:entry-point')
void keyboardToolbarMain() {
WidgetsFlutterBinding.ensureInitialized();
const channel = MethodChannel('YOUR_CHANNEL_NAME');
channel.invokeMethod('flutterReady');
runApp(const KeyboardToolbarApp());
}
Code: Select all
class KeyboardToolbarApp extends StatefulWidget {
const KeyboardToolbarApp({super.key});
@override
State createState() => _KeyboardToolbarAppState();
}
class _KeyboardToolbarAppState extends State {
static const MethodChannel _channel = MethodChannel('keyboard_api');
@override
void initState() {
super.initState();
_channel.setMethodCallHandler(_handleMethodCall);
// Notify Kotlin that Flutter is ready
WidgetsBinding.instance.addPostFrameCallback((_) async {
print("🟢 FLUTTER READY SENT");
await _channel.invokeMethod('flutterReady');
});
}
Future _handleMethodCall(MethodCall call) async {
print("🔥 METHOD RECEIVED FROM KOTLIN: ${call.method}");
try {
if (call.method == "callApi") {
final String text = call.arguments['text'];
final String feature = call.arguments['feature'];
Log.d("Flutter", "Received API call: $feature - $text");
final result = await callKeyboardApi(text, feature);
Log.d("Flutter", "API result: $result");
return result; // This is sent back to Kotlin
}
} catch (e) {
Log.e("Flutter", "Error in handleMethodCall: $e");
return "Error: $e";
}
return null;
}
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: SizedBox.shrink(),
);
}
}
Future callKeyboardApi(String text, String feature) async {
try {
Log.d("Flutter", "Calling keyboard API: $feature");
late String url;
if (feature == "change-tone") {
url = ApiEndpoints.changeToneRephrase(feature, "professional");
} else {
url = ApiEndpoints.rePhrase(feature);
}
Log.d("Flutter", "URL: $url, Text: $text");
final request = NetworkRequest(
url: url,
method: NetworkRequestType.POST,
params: {"text": text},
callback: (data, code, message) {},
);
final response = await AppNetworking.instance.callRequestAsync(request);
Log.d("Flutter", "Raw response: $response");
if (response == null || response.isEmpty) {
return "No response from API";
}
final parsed = RephraseResponse.fromJson(response);
return parsed.data ?? "No data received";
} catch (e) {
Log.e("Flutter", "Error in callKeyboardApi: $e");
return "Error: $e";
}
}
// Add this simple logging class
class Log {
static void d(String tag, String message) {
print("[$tag] $message");
}
static void e(String tag, String message) {
print("ERROR [$tag] $message");
}
}
Mobile version