Я просто пытался вызвать API на пользовательской клавиатуре, например, когда выделен текст и нажмите любую кнопку для примера. Проверьте грамматику, затем API должен вызвать в соответствии с моим кодом и показать результат на клавиатуре, где отображаются буквы, там будет показан ответ, когда API вызывается, он показывает бесконечную загрузку, и ничего не печатает журналы, и ничего не отображается, я даже не знаю, что является точным проблема
RewriterkeyboardService.kt
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)
//
GeneratedPluginRegistrant.registerWith(flutterEngine)
//
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()
}
}
keyboard_view.xml
keyboard_toolbar.dart
@pragma('vm:entry-point')
void keyboardToolbarMain() {
WidgetsFlutterBinding.ensureInitialized();
const channel = MethodChannel('YOUR_CHANNEL_NAME');
channel.invokeMethod('flutterReady');
runApp(const KeyboardToolbarApp());
}
keyboard_toolbar_app.dart
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("
await _channel.invokeMethod('flutterReady');
});
}
Future _handleMethodCall(MethodCall call) async {
print("
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");
}
}
Подробнее здесь: https://stackoverflow.com/questions/798 ... er-android
Мобильная версия