feat(android): integrate web api & native AI chat button (#10239)

This commit is contained in:
Aki Chang
2025-02-18 16:23:25 +08:00
committed by GitHub
parent 892fd16f52
commit 3f4b7ec51e
15 changed files with 302 additions and 23 deletions

View File

@@ -67,12 +67,15 @@ dependencies {
implementation project(':capacitor-cordova-android-plugins')
implementation project(':service')
implementation libs.kotlinx.coroutines.core
implementation libs.kotlinx.coroutines.android
implementation libs.androidx.appcompat
implementation libs.androidx.browser
implementation libs.androidx.coordinatorlayout
implementation libs.androidx.core.splashscreen
implementation libs.androidx.core.ktx
implementation libs.androidx.material3
implementation libs.apollo.runtime
implementation libs.google.material
implementation libs.jna
testImplementation libs.junit
androidTestImplementation libs.androidx.junit

View File

@@ -1,26 +1,80 @@
package app.affine.pro
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
import android.content.res.ColorStateList
import android.view.Gravity
import android.view.View
import android.widget.Toast
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.view.updateMargins
import androidx.lifecycle.lifecycleScope
import app.affine.pro.plugin.AIButtonPlugin
import app.affine.pro.plugin.AffineThemePlugin
import app.affine.pro.utils.dp
import com.getcapacitor.BridgeActivity
import com.getcapacitor.plugin.CapacitorCookies
import com.getcapacitor.plugin.CapacitorHttp
import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.coroutines.launch
class MainActivity : BridgeActivity() {
class MainActivity : BridgeActivity(), AIButtonPlugin.Callback, AffineThemePlugin.Callback,
View.OnClickListener {
init {
registerPlugins(
listOf(
AffineThemePlugin::class.java,
AIButtonPlugin::class.java,
CapacitorHttp::class.java,
CapacitorCookies::class.java,
)
)
}
@RequiresApi(Build.VERSION_CODES.R)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
private val fab: FloatingActionButton by lazy {
FloatingActionButton(this).apply {
visibility = View.GONE
layoutParams = CoordinatorLayout.LayoutParams(dp(52), dp(52)).apply {
gravity = Gravity.END or Gravity.BOTTOM
updateMargins(0, 0, dp(24), dp(86))
}
customSize = dp(52)
setImageResource(R.drawable.ic_ai)
setOnClickListener(this@MainActivity)
val parent = bridge.webView.parent as CoordinatorLayout
parent.addView(this)
}
}
override fun present() {
lifecycleScope.launch {
fab.show()
}
}
override fun dismiss() {
lifecycleScope.launch {
fab.hide()
}
}
override fun onThemeChanged(darkMode: Boolean) {
lifecycleScope.launch {
fab.backgroundTintList = ColorStateList.valueOf(
ContextCompat.getColor(
this@MainActivity,
if (darkMode) {
R.color.layer_background_primary_dark
} else {
R.color.layer_background_primary
}
)
)
}
}
override fun onClick(v: View) {
Toast.makeText(this, "TODO: Start AI chat~", Toast.LENGTH_SHORT).show()
}
}

View File

@@ -0,0 +1,27 @@
package app.affine.pro.plugin
import com.getcapacitor.Plugin
import com.getcapacitor.PluginCall
import com.getcapacitor.PluginMethod
import com.getcapacitor.annotation.CapacitorPlugin
@CapacitorPlugin(name = "AIButton")
class AIButtonPlugin : Plugin() {
interface Callback {
fun present()
fun dismiss()
}
@PluginMethod
fun present(call: PluginCall) {
(activity as? Callback)?.present()
call.resolve()
}
@PluginMethod
fun dismiss(call: PluginCall) {
(activity as? Callback)?.dismiss()
call.resolve()
}
}

View File

@@ -0,0 +1,20 @@
package app.affine.pro.plugin
import com.getcapacitor.Plugin
import com.getcapacitor.PluginCall
import com.getcapacitor.PluginMethod
import com.getcapacitor.annotation.CapacitorPlugin
@CapacitorPlugin(name = "AffineTheme")
class AffineThemePlugin : Plugin() {
interface Callback {
fun onThemeChanged(darkMode: Boolean)
}
@PluginMethod
fun onThemeChanged(call: PluginCall) {
(bridge.activity as? Callback)?.onThemeChanged(call.data.optBoolean("darkMode"))
call.resolve()
}
}

View File

@@ -0,0 +1,10 @@
package app.affine.pro.utils
import android.content.Context
import android.util.TypedValue
fun Context.dp(dp: Int) = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),
resources.displayMetrics
).toInt()

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12.174,5.491C12.133,5.13 11.828,4.858 11.465,4.857C11.102,4.857 10.796,5.129 10.754,5.49C10.484,7.835 9.783,9.484 8.651,10.616C7.52,11.747 5.871,12.448 3.525,12.719C3.164,12.761 2.892,13.066 2.893,13.429C2.893,13.792 3.166,14.097 3.526,14.138C5.833,14.4 7.518,15.101 8.676,16.238C9.83,17.371 10.546,19.02 10.752,21.349C10.785,21.718 11.094,22 11.465,22C11.835,22 12.144,21.716 12.176,21.347C12.374,19.056 13.089,17.373 14.249,16.213C15.408,15.054 17.092,14.339 19.383,14.14C19.752,14.108 20.035,13.8 20.035,13.429C20.036,13.059 19.753,12.75 19.384,12.717C17.055,12.51 15.406,11.794 14.273,10.64C13.136,9.482 12.435,7.798 12.174,5.491Z"
android:fillColor="#1E96EB"/>
<path
android:pathData="M19.835,2.247C19.819,2.106 19.701,2 19.559,2C19.418,2 19.299,2.106 19.283,2.246C19.178,3.158 18.905,3.8 18.465,4.239C18.025,4.679 17.384,4.952 16.472,5.057C16.332,5.074 16.226,5.192 16.226,5.334C16.226,5.475 16.332,5.593 16.472,5.609C17.369,5.711 18.025,5.984 18.475,6.426C18.924,6.866 19.202,7.508 19.283,8.413C19.295,8.557 19.416,8.667 19.56,8.667C19.704,8.667 19.824,8.556 19.836,8.413C19.913,7.522 20.191,6.867 20.642,6.416C21.093,5.965 21.748,5.687 22.639,5.61C22.782,5.598 22.892,5.478 22.893,5.334C22.893,5.19 22.783,5.069 22.639,5.057C21.734,4.976 21.092,4.698 20.652,4.249C20.209,3.799 19.937,3.144 19.835,2.247Z"
android:fillColor="#1E96EB"/>
</vector>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="layer.background.primary">#FFFFFF</color>
<color name="layer.background.primary.dark">#141414</color>
</resources>

View File

@@ -1,13 +1,15 @@
[versions]
androidxEspressoCoreVersion = "3.6.1"
androidxJunitVersion = "1.2.1"
androidxEspressoCore = "3.6.1"
androidxJunit = "1.2.1"
browser = "1.8.0"
coreKtx = "1.15.0"
coreSplashScreenVersion = "1.0.1"
material = "1.12.0"
material3 = "1.3.1"
coreSplashScreen = "1.0.1"
jna = "5.16.0"
junitVersion = "4.13.2"
kotlin = "2.1.10"
kotlinxCoroutinesCore = "1.10.1"
kotlinxCoroutines = "1.10.1"
rustAndroid = "0.9.6"
appcompat = "1.7.0"
coordinatorLayout = "1.2.0"
@@ -20,12 +22,15 @@ androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "a
androidx-browser = { module = "androidx.browser:browser", version.ref = "browser" }
androidx-coordinatorlayout = { module = "androidx.coordinatorlayout:coordinatorlayout", version.ref = "coordinatorLayout" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashScreenVersion" }
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidxEspressoCoreVersion" }
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidxJunitVersion" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashScreen" }
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidxEspressoCore" }
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidxJunit" }
androidx-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutines"}
google-services = { module = "com.google.gms:google-services", version.ref = "googleServices" }
google-material = { module = "com.google.android.material:material", version.ref = "material" }
android-gradlePlugin = { module = "com.android.tools.build:gradle", version.ref = "androidGradlePlugin" }
jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
junit = { module = "junit:junit", version.ref = "junitVersion" }