fix(android): fix edge-to-edge (#12453)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added the ability to retrieve the system navigation bar height on Android devices.

- **Bug Fixes**
  - Removed duplicate internal code to improve stability.

- **Chores**
  - Removed the dependency on the edge-to-edge support package and related configuration and code.
  - Updated configuration to adjust margins for edge-to-edge layouts.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
aki-chang-dev
2025-05-22 09:57:50 +00:00
parent 6d662b8a54
commit d91e64b46b
10 changed files with 91 additions and 83 deletions

View File

@@ -12,7 +12,6 @@ dependencies {
implementation project(':capacitor-app')
implementation project(':capacitor-keyboard')
implementation project(':capacitor-status-bar')
implementation project(':capawesome-capacitor-android-edge-to-edge-support')
implementation project(':capgo-inappbrowser')
}

View File

@@ -1,10 +1,13 @@
package app.affine.pro
import android.content.res.ColorStateList
import android.os.Bundle
import android.view.Gravity
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateMargins
import androidx.lifecycle.lifecycleScope
import app.affine.pro.ai.AIActivity
@@ -64,6 +67,16 @@ class MainActivity : BridgeActivity(), AIButtonPlugin.Callback, AffineThemePlugi
}
}
private var naviHeight = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { v, insets ->
naviHeight = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom
ViewCompat.onApplyWindowInsets(v, insets)
}
}
override fun load() {
super.load()
AuthInitializer.initialize(bridge)
@@ -96,6 +109,10 @@ class MainActivity : BridgeActivity(), AIButtonPlugin.Callback, AffineThemePlugi
}
}
override fun getSystemNaviBarHeight(): Int {
return naviHeight
}
override fun onClick(v: View) {
lifecycleScope.launch {
webService.update(bridge)

View File

@@ -1,5 +1,6 @@
package app.affine.pro.plugin
import com.getcapacitor.JSObject
import com.getcapacitor.Plugin
import com.getcapacitor.PluginCall
import com.getcapacitor.PluginMethod
@@ -11,6 +12,7 @@ class AffineThemePlugin : Plugin() {
interface Callback {
fun onThemeChanged(darkMode: Boolean)
fun getSystemNaviBarHeight(): Int
}
@PluginMethod
@@ -20,4 +22,10 @@ class AffineThemePlugin : Plugin() {
(bridge.activity as? Callback)?.onThemeChanged(darkMode)
call.resolve()
}
@PluginMethod
fun getSystemNaviBarHeight(call: PluginCall) {
val height = (bridge.activity as? Callback)?.getSystemNaviBarHeight() ?: 0
call.resolve(JSObject().put("height", height))
}
}

View File

@@ -1303,6 +1303,70 @@ inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
* @suppress
* */
object NoPointer
/**
* The cleaner interface for Object finalization code to run.
* This is the entry point to any implementation that we're using.
*
* The cleaner registers objects and returns cleanables, so now we are
* defining a `UniffiCleaner` with a `UniffiClenaer.Cleanable` to abstract the
* different implmentations available at compile time.
*
* @suppress
*/
interface UniffiCleaner {
interface Cleanable {
fun clean()
}
fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable
companion object
}
// The fallback Jna cleaner, which is available for both Android, and the JVM.
private class UniffiJnaCleaner : UniffiCleaner {
private val cleaner = com.sun.jna.internal.Cleaner.getCleaner()
override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable =
UniffiJnaCleanable(cleaner.register(value, cleanUpTask))
}
private class UniffiJnaCleanable(
private val cleanable: com.sun.jna.internal.Cleaner.Cleanable,
) : UniffiCleaner.Cleanable {
override fun clean() = cleanable.clean()
}
// We decide at uniffi binding generation time whether we were
// using Android or not.
// There are further runtime checks to chose the correct implementation
// of the cleaner.
private fun UniffiCleaner.Companion.create(): UniffiCleaner =
try {
// For safety's sake: if the library hasn't been run in android_cleaner = true
// mode, but is being run on Android, then we still need to think about
// Android API versions.
// So we check if java.lang.ref.Cleaner is there, and use that…
java.lang.Class.forName("java.lang.ref.Cleaner")
JavaLangRefCleaner()
} catch (e: ClassNotFoundException) {
// … otherwise, fallback to the JNA cleaner.
UniffiJnaCleaner()
}
private class JavaLangRefCleaner : UniffiCleaner {
val cleaner = java.lang.ref.Cleaner.create()
override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable =
JavaLangRefCleanable(cleaner.register(value, cleanUpTask))
}
private class JavaLangRefCleanable(
val cleanable: java.lang.ref.Cleaner.Cleanable
) : UniffiCleaner.Cleanable {
override fun clean() = cleanable.clean()
}
/**
* @suppress
@@ -1529,70 +1593,6 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
//
/**
* The cleaner interface for Object finalization code to run.
* This is the entry point to any implementation that we're using.
*
* The cleaner registers objects and returns cleanables, so now we are
* defining a `UniffiCleaner` with a `UniffiClenaer.Cleanable` to abstract the
* different implmentations available at compile time.
*
* @suppress
*/
interface UniffiCleaner {
interface Cleanable {
fun clean()
}
fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable
companion object
}
// The fallback Jna cleaner, which is available for both Android, and the JVM.
private class UniffiJnaCleaner : UniffiCleaner {
private val cleaner = com.sun.jna.internal.Cleaner.getCleaner()
override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable =
UniffiJnaCleanable(cleaner.register(value, cleanUpTask))
}
private class UniffiJnaCleanable(
private val cleanable: com.sun.jna.internal.Cleaner.Cleanable,
) : UniffiCleaner.Cleanable {
override fun clean() = cleanable.clean()
}
// We decide at uniffi binding generation time whether we were
// using Android or not.
// There are further runtime checks to chose the correct implementation
// of the cleaner.
private fun UniffiCleaner.Companion.create(): UniffiCleaner =
try {
// For safety's sake: if the library hasn't been run in android_cleaner = true
// mode, but is being run on Android, then we still need to think about
// Android API versions.
// So we check if java.lang.ref.Cleaner is there, and use that…
java.lang.Class.forName("java.lang.ref.Cleaner")
JavaLangRefCleaner()
} catch (e: ClassNotFoundException) {
// … otherwise, fallback to the JNA cleaner.
UniffiJnaCleaner()
}
private class JavaLangRefCleaner : UniffiCleaner {
val cleaner = java.lang.ref.Cleaner.create()
override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable =
JavaLangRefCleanable(cleaner.register(value, cleanUpTask))
}
private class JavaLangRefCleanable(
val cleanable: java.lang.ref.Cleaner.Cleanable
) : UniffiCleaner.Cleanable {
override fun clean() = cleanable.clean()
}
public interface DocStoragePoolInterface {
suspend fun `clearClocks`(`universalId`: kotlin.String)

View File

@@ -11,8 +11,5 @@ project(':capacitor-keyboard').projectDir = new File('../../../../../node_module
include ':capacitor-status-bar'
project(':capacitor-status-bar').projectDir = new File('../../../../../node_modules/@capacitor/status-bar/android')
include ':capawesome-capacitor-android-edge-to-edge-support'
project(':capawesome-capacitor-android-edge-to-edge-support').projectDir = new File('../../../../../node_modules/@capawesome/capacitor-android-edge-to-edge-support/android')
include ':capgo-inappbrowser'
project(':capgo-inappbrowser').projectDir = new File('../../../../../node_modules/@capgo/inappbrowser/android')

View File

@@ -25,6 +25,7 @@ const config: CapacitorConfig & AppConfig = {
keystoreAliasPassword: process.env.AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD,
releaseType: 'AAB',
},
adjustMarginsForEdgeToEdge: 'force',
},
server: {
cleartext: true,

View File

@@ -23,7 +23,6 @@
"@capacitor/core": "^7.0.0",
"@capacitor/keyboard": "^7.0.0",
"@capacitor/status-bar": "^7.0.0",
"@capawesome/capacitor-android-edge-to-edge-support": "^7.0.0",
"@capgo/inappbrowser": "^7.1.0",
"@sentry/react": "^9.2.0",
"@toeverything/infra": "workspace:*",

View File

@@ -40,7 +40,6 @@ import {
import { App as CapacitorApp } from '@capacitor/app';
import { Keyboard } from '@capacitor/keyboard';
import { StatusBar, Style } from '@capacitor/status-bar';
import { EdgeToEdge } from '@capawesome/capacitor-android-edge-to-edge-support';
import { InAppBrowser } from '@capgo/inappbrowser';
import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra';
import { OpClient } from '@toeverything/infra/op';
@@ -329,9 +328,6 @@ const ThemeProvider = () => {
? Style.Light
: Style.Default,
}).catch(console.error);
EdgeToEdge.setBackgroundColor({
color: resolvedTheme === 'dark' ? '#000000' : '#F5F5F5',
}).catch(console.error);
AffineTheme.onThemeChanged({
darkMode: resolvedTheme === 'dark',
}).catch(console.error);

View File

@@ -1,3 +1,4 @@
export interface AffineThemePlugin {
onThemeChanged(options: { darkMode: boolean }): Promise<void>;
getSystemNaviBarHeight(): Promise<{ height: number }>;
}