orientation

This commit is contained in:
agra
2026-04-22 16:22:45 +03:00
parent 3032442c31
commit 2113537078
14 changed files with 231 additions and 16 deletions

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.10)
project(ux_keyboard)
add_library(ux_keyboard SHARED keyboard_bridge.c)
add_library(ux_keyboard SHARED keyboard_bridge.c sensor_bridge.c)
target_link_libraries(ux_keyboard log)
# 16KB page size support (required for Android 15+)

View File

@@ -0,0 +1,18 @@
#include <jni.h>
#include <stdint.h>
// Shared state — Kotlin writes, Dart reads via FFI.
// Encoded as Surface rotation convention:
// 0 = portraitUp, 1 = landscapeLeft, 2 = portraitDown, 3 = landscapeRight.
static int32_t g_device_orientation = 0;
// --- Dart FFI reads ---
int32_t ux_device_orientation(void) { return g_device_orientation; }
// --- Kotlin JNI writes ---
JNIEXPORT void JNICALL
Java_io_swipelab_ux_SensorBridge_nSetDeviceOrientation(JNIEnv *env, jclass cls, jint v) {
g_device_orientation = v;
}

View File

@@ -7,12 +7,11 @@ import android.view.ViewTreeObserver
import android.view.WindowInsets
import android.view.WindowInsetsAnimation
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
class KeyboardPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware {
class KeyboardPlugin : NativePlugin, MethodChannel.MethodCallHandler {
private var methodChannel: MethodChannel? = null
private var activity: Activity? = null
private var windowFocusListener: ViewTreeObserver.OnWindowFocusChangeListener? = null

View File

@@ -0,0 +1,13 @@
package io.swipelab.ux
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
interface NativePlugin {
fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {}
fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {}
fun onAttachedToActivity(binding: ActivityPluginBinding) {}
fun onDetachedFromActivity() {}
fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) = onAttachedToActivity(binding)
fun onDetachedFromActivityForConfigChanges() = onDetachedFromActivity()
}

View File

@@ -0,0 +1,9 @@
package io.swipelab.ux
object SensorBridge {
init {
System.loadLibrary("ux_keyboard")
}
@JvmStatic external fun nSetDeviceOrientation(v: Int)
}

View File

@@ -0,0 +1,55 @@
package io.swipelab.ux
import android.app.Activity
import android.view.OrientationEventListener
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
class SensorPlugin : NativePlugin {
private var listener: OrientationEventListener? = null
private var current: Int = 0
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
attach(binding.activity)
}
override fun onDetachedFromActivity() {
detach()
}
private fun attach(activity: Activity) {
detach()
val l = object : OrientationEventListener(activity) {
override fun onOrientationChanged(degrees: Int) {
if (degrees == ORIENTATION_UNKNOWN) return
val bucket = bucketFor(degrees, current)
if (bucket == current) return
current = bucket
SensorBridge.nSetDeviceOrientation(bucket)
}
}
if (l.canDetectOrientation()) l.enable()
listener = l
}
private fun detach() {
listener?.disable()
listener = null
}
companion object {
internal fun bucketFor(degrees: Int, previous: Int): Int {
val d = ((degrees % 360) + 360) % 360
return when {
d in 0..30 || d in 330..359 -> 0
d in 60..120 -> 3
d in 150..210 -> 2
d in 240..300 -> 1
d in 31..59 -> if (previous == 0 || previous == 3) previous else 0
d in 121..149 -> if (previous == 3 || previous == 2) previous else 3
d in 211..239 -> if (previous == 2 || previous == 1) previous else 2
d in 301..329 -> if (previous == 1 || previous == 0) previous else 1
else -> previous
}
}
}
}

View File

@@ -0,0 +1,30 @@
package io.swipelab.ux
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
class UxPlugin : FlutterPlugin, ActivityAware {
private val plugins: List<NativePlugin> = listOf(
KeyboardPlugin(),
SensorPlugin(),
)
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) =
plugins.forEach { it.onAttachedToEngine(binding) }
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) =
plugins.forEach { it.onDetachedFromEngine(binding) }
override fun onAttachedToActivity(binding: ActivityPluginBinding) =
plugins.forEach { it.onAttachedToActivity(binding) }
override fun onDetachedFromActivity() =
plugins.forEach { it.onDetachedFromActivity() }
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) =
plugins.forEach { it.onReattachedToActivityForConfigChanges(binding) }
override fun onDetachedFromActivityForConfigChanges() =
plugins.forEach { it.onDetachedFromActivityForConfigChanges() }
}