build: Android's built-in Kotlin, the three plugins applying KGP themselves patched in third_party/

AGP 9 compiles Kotlin itself (android.builtInKotlin=true in every app),
and warns that applying the Kotlin Gradle Plugin will soon fail.
flutter_web_auth_2 5.1.0, keyboard_height_plugin 0.3.0 and flutter_tts
4.2.5 still apply it unconditionally, so their pub releases are copied
into third_party/ with only their android/build.gradle changed, and
the root overrides them by path (third_party/README.md says when to
drop each). The servers' images copy third_party/, as pub refuses an
override whose path is missing. third_party/ isn't our own code: left
out of the analysis and the format check.

All four apps' APKs build without the warning. A Kotlin daemon's
session file that slipped into Train's android/ goes, and every app's
android/.gitignore keeps .kotlin/ out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-04 00:15:55 +01:00
commit 438ce20dbf
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
4 changed files with 8440 additions and 23 deletions

View file

@ -0,0 +1,53 @@
group 'com.nschairer.keyboard_height_plugin'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '2.1.0'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.6.0'
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 34
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
minSdkVersion 16
}
if (project.android.hasProperty("namespace")) {
namespace 'com.nschairer.keyboard_height_plugin'
}
}
// Halkyon's patch (4 Nov 2026): Kotlin built into the Android Gradle plugin
// (AGP 9, android.builtInKotlin=true) rather than the Kotlin Gradle plugin
// applied here, as Flutter's guide for plugin authors has it.
kotlin {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8
}
}

View file

@ -0,0 +1 @@
rootProject.name = 'keyboard_height_plugin'

View file

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.nschairer.keyboard_height_plugin">
</manifest>

View file

@ -0,0 +1,93 @@
package com.nschairer.keyboard_height_plugin
import android.graphics.Rect
import android.os.Build
import androidx.annotation.NonNull
import android.view.ViewTreeObserver
import androidx.annotation.RequiresApi
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
class KeyboardHeightPlugin : FlutterPlugin, EventChannel.StreamHandler, ActivityAware {
private val keyboardHeightEventChannelName = "keyboardHeightEventChannel"
private var eventSink: EventChannel.EventSink? = null
private var eventChannel: EventChannel? = null
private var activityPluginBinding: ActivityPluginBinding? = null
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, keyboardHeightEventChannelName)
eventChannel?.setStreamHandler(this)
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
eventChannel?.setStreamHandler(null)
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
val rootView = activityPluginBinding?.activity?.window?.decorView?.rootView
rootView?.viewTreeObserver?.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
val r = Rect()
rootView.getWindowVisibleDisplayFrame(r)
val screenHeight = rootView.height
val navigationBarHeight = getNavigationBarHeight();
var keypadHeight = screenHeight - r.bottom
if (isNavigationBarVisible()) {
keypadHeight -= navigationBarHeight
}
val displayMetrics = activityPluginBinding?.activity?.resources?.displayMetrics
val logicalKeypadHeight = keypadHeight / (displayMetrics?.density ?: 1f)
if (keypadHeight > screenHeight * 0.15) {
events?.success(logicalKeypadHeight.toDouble())
} else {
events?.success(0.0)
}
}
})
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
private fun getNavigationBarHeight(): Int {
val resourceId = activityPluginBinding?.activity?.resources?.getIdentifier("navigation_bar_height", "dimen", "android")
return if (resourceId != null && resourceId > 0) {
activityPluginBinding?.activity?.resources?.getDimensionPixelSize(resourceId) ?: 0
} else {
0
}
}
private fun isNavigationBarVisible(): Boolean {
val decorView = activityPluginBinding?.activity?.window?.decorView
val rootWindowInsets = decorView?.rootWindowInsets ?: return false
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
rootWindowInsets.isVisible(android.view.WindowInsets.Type.navigationBars())
} else {
val systemWindowInsetBottom = rootWindowInsets.systemWindowInsetBottom
systemWindowInsetBottom > 0
}
}
// Implement ActivityAware methods
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activityPluginBinding = binding
}
override fun onDetachedFromActivityForConfigChanges() {
activityPluginBinding = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activityPluginBinding = binding
}
override fun onDetachedFromActivity() {
activityPluginBinding = null
}
}