mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-27 02:42:25 +08:00
chore(android): integrate firebase-crashlytics (#11808)
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
/build/*
|
||||
!/build/.npmkeep
|
||||
google-services.json
|
||||
|
||||
@@ -3,13 +3,15 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
||||
|
||||
plugins {
|
||||
alias libs.plugins.android.application
|
||||
alias libs.plugins.compose
|
||||
alias libs.plugins.hilt
|
||||
alias libs.plugins.kotlin.android
|
||||
alias libs.plugins.kotlin.parcelize
|
||||
alias libs.plugins.kotlin.serialization
|
||||
alias libs.plugins.ksp
|
||||
alias libs.plugins.rust.android
|
||||
alias libs.plugins.compose
|
||||
alias libs.plugins.google.service
|
||||
alias libs.plugins.firebase.crashlytics
|
||||
alias libs.plugins.hilt
|
||||
}
|
||||
|
||||
apply from: 'capacitor.build.gradle'
|
||||
@@ -106,6 +108,7 @@ dependencies {
|
||||
implementation libs.androidx.coordinatorlayout
|
||||
implementation libs.androidx.core.ktx
|
||||
implementation libs.androidx.core.splashscreen
|
||||
implementation libs.androidx.datastore.preferences
|
||||
implementation libs.androidx.navigation.fragment
|
||||
implementation libs.androidx.navigation.ui.ktx
|
||||
|
||||
@@ -118,8 +121,11 @@ dependencies {
|
||||
implementation libs.kotlinx.coroutines.android
|
||||
implementation libs.kotlinx.serialization.json
|
||||
|
||||
def okhttpBom = platform(libs.okhttp.bom)
|
||||
implementation okhttpBom
|
||||
implementation platform(libs.firebase.bom)
|
||||
implementation libs.firebase.analytics
|
||||
implementation libs.firebase.crashlytics
|
||||
|
||||
implementation platform(libs.okhttp.bom)
|
||||
implementation libs.okhttp
|
||||
implementation libs.okhttp.coroutines
|
||||
implementation libs.okhttp.logging
|
||||
@@ -130,15 +136,6 @@ dependencies {
|
||||
androidTestImplementation libs.androidx.espresso.core
|
||||
}
|
||||
|
||||
try {
|
||||
def servicesJSON = file('google-services.json')
|
||||
if (servicesJSON.text) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
|
||||
}
|
||||
|
||||
cargo {
|
||||
module = "../../../../mobile-native"
|
||||
libname = "affine_mobile_native"
|
||||
|
||||
@@ -68,3 +68,8 @@
|
||||
-keepclassmembers public class **$$serializer {
|
||||
private ** descriptor;
|
||||
}
|
||||
|
||||
# Keep file names and line numbers.
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
# Keep custom exceptions.
|
||||
-keep public class * extends java.lang.Exception
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
package app.affine.pro
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import app.affine.pro.service.CookieStore
|
||||
import app.affine.pro.utils.dataStore
|
||||
import app.affine.pro.utils.get
|
||||
import app.affine.pro.utils.logger.AffineDebugTree
|
||||
import app.affine.pro.utils.logger.CrashlyticsTree
|
||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||
import com.google.firebase.crashlytics.setCustomKeys
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import timber.log.Timber
|
||||
|
||||
@HiltAndroidApp
|
||||
@@ -9,8 +24,47 @@ class AffineApp : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
||||
_context = applicationContext
|
||||
// init logger
|
||||
Timber.plant(if (BuildConfig.DEBUG) AffineDebugTree() else CrashlyticsTree())
|
||||
// init capacitor config
|
||||
CapacitorConfig.init(baseContext)
|
||||
// init crashlytics
|
||||
Firebase.crashlytics.setCustomKeys {
|
||||
key("affine_version", CapacitorConfig.getAffineVersion())
|
||||
}
|
||||
// init cookies from local
|
||||
MainScope().launch(Dispatchers.IO) {
|
||||
val sessionCookieStr = applicationContext.dataStore.get(CookieStore.AFFINE_SESSION)
|
||||
val userIdCookieStr = applicationContext.dataStore.get(CookieStore.AFFINE_USER_ID)
|
||||
if (sessionCookieStr.isEmpty() || userIdCookieStr.isEmpty()) {
|
||||
Timber.i("[init] user has not signed in yet.")
|
||||
return@launch
|
||||
}
|
||||
Timber.i("[init] user already signed in.")
|
||||
try {
|
||||
val cookies = listOf(
|
||||
Cookie.parse(BuildConfig.BASE_URL.toHttpUrl(), sessionCookieStr)
|
||||
?: error("Parse session cookie fail:[ cookie = $sessionCookieStr ]"),
|
||||
Cookie.parse(BuildConfig.BASE_URL.toHttpUrl(), userIdCookieStr)
|
||||
?: error("Parse user id cookie fail:[ cookie = $userIdCookieStr ]"),
|
||||
)
|
||||
CookieStore.saveCookies(BuildConfig.BASE_URL.toHttpUrl().host, cookies)
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e, "[init] load persistent cookies fail.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
_context = null
|
||||
super.onTerminate()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private var _context: Context? = null
|
||||
|
||||
fun context() = requireNotNull(_context)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package app.affine.pro.ai
|
||||
package app.affine.pro
|
||||
|
||||
enum class Prompt(val value: String) {
|
||||
Summary("Summary"),
|
||||
@@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
@HiltViewModel
|
||||
class ChatViewModel @Inject constructor(
|
||||
@@ -37,10 +36,10 @@ class ChatViewModel @Inject constructor(
|
||||
workspaceId = webRepo.workspaceId(),
|
||||
docId = webRepo.docId(),
|
||||
).getOrElse {
|
||||
Timber.d("Create session failed")
|
||||
Timber.w(it, "Create session failed")
|
||||
return@launch
|
||||
}
|
||||
Timber.d("Create session: $sessionId")
|
||||
Timber.i("Create session success:[ sessionId = $sessionId].")
|
||||
val historyMessages = graphQLRepo.getCopilotHistories(
|
||||
workspaceId = webRepo.workspaceId(),
|
||||
docId = webRepo.docId(),
|
||||
@@ -60,16 +59,12 @@ class ChatViewModel @Inject constructor(
|
||||
sessionId = sessionId,
|
||||
message = message,
|
||||
).onSuccess { messageId ->
|
||||
Timber.d("send message: $messageId")
|
||||
Timber.i("send message: $messageId")
|
||||
sseRepo.messageStream(sessionId, messageId)
|
||||
.onEach {
|
||||
Timber.d("$coroutineContext")
|
||||
Timber.d("on message: ${it.getOrNull()}")
|
||||
Timber.d("On sse message: ${it.getOrNull()}")
|
||||
}
|
||||
.flowOn(Dispatchers.IO)
|
||||
.onEach {
|
||||
Timber.d("$coroutineContext")
|
||||
}
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -80,7 +75,7 @@ class ChatViewModel @Inject constructor(
|
||||
docId = webRepo.docId(),
|
||||
).onSuccess { id ->
|
||||
sessionId = id
|
||||
Timber.d("Create session: $id")
|
||||
Timber.i("Create session: $id")
|
||||
sendMessage()
|
||||
}.onFailure {
|
||||
Timber.e(it, "Create session failed.")
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.getcapacitor.Plugin
|
||||
import com.getcapacitor.PluginCall
|
||||
import com.getcapacitor.PluginMethod
|
||||
import com.getcapacitor.annotation.CapacitorPlugin
|
||||
import timber.log.Timber
|
||||
|
||||
@CapacitorPlugin(name = "AIButton")
|
||||
class AIButtonPlugin : Plugin() {
|
||||
@@ -16,6 +17,7 @@ class AIButtonPlugin : Plugin() {
|
||||
@PluginMethod
|
||||
fun present(call: PluginCall) {
|
||||
launch {
|
||||
Timber.i("present AIButton")
|
||||
(activity as? Callback)?.present()
|
||||
call.resolve()
|
||||
}
|
||||
@@ -24,6 +26,7 @@ class AIButtonPlugin : Plugin() {
|
||||
@PluginMethod
|
||||
fun dismiss(call: PluginCall) {
|
||||
launch {
|
||||
Timber.i("dismiss AIButton")
|
||||
(activity as? Callback)?.dismiss()
|
||||
call.resolve()
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.getcapacitor.Plugin
|
||||
import com.getcapacitor.PluginCall
|
||||
import com.getcapacitor.PluginMethod
|
||||
import com.getcapacitor.annotation.CapacitorPlugin
|
||||
import timber.log.Timber
|
||||
|
||||
@CapacitorPlugin(name = "AffineTheme")
|
||||
class AffineThemePlugin : Plugin() {
|
||||
@@ -14,7 +15,9 @@ class AffineThemePlugin : Plugin() {
|
||||
|
||||
@PluginMethod
|
||||
fun onThemeChanged(call: PluginCall) {
|
||||
(bridge.activity as? Callback)?.onThemeChanged(call.data.optBoolean("darkMode"))
|
||||
val darkMode = call.data.optBoolean("darkMode")
|
||||
Timber.i("onThemeChanged:[ darkMode = $darkMode ]")
|
||||
(bridge.activity as? Callback)?.onThemeChanged(darkMode)
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
package app.affine.pro.plugin
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import app.affine.pro.AffineApp
|
||||
import app.affine.pro.CapacitorConfig
|
||||
import app.affine.pro.service.CookieStore
|
||||
import app.affine.pro.service.OkHttp
|
||||
import app.affine.pro.utils.clear
|
||||
import app.affine.pro.utils.dataStore
|
||||
import com.getcapacitor.JSObject
|
||||
import com.getcapacitor.Plugin
|
||||
import com.getcapacitor.PluginCall
|
||||
@@ -16,6 +19,7 @@ import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.coroutines.executeAsync
|
||||
import org.json.JSONObject
|
||||
import timber.log.Timber
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@CapacitorPlugin(name = "Auth")
|
||||
@@ -23,120 +27,18 @@ class AuthPlugin : Plugin() {
|
||||
|
||||
@PluginMethod
|
||||
fun signInMagicLink(call: PluginCall) {
|
||||
launch(Dispatchers.IO) {
|
||||
try {
|
||||
val endpoint = call.getStringEnsure("endpoint")
|
||||
val email = call.getStringEnsure("email")
|
||||
val token = call.getStringEnsure("token")
|
||||
val clientNonce = call.getString("clientNonce")
|
||||
val body = JSONObject()
|
||||
.apply {
|
||||
put("email", email)
|
||||
put("token", token)
|
||||
put("client_nonce", clientNonce)
|
||||
}
|
||||
.toString()
|
||||
.toRequestBody("application/json".toMediaTypeOrNull())
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$endpoint/api/auth/magic-link")
|
||||
.header("x-affine-version", CapacitorConfig.getAffineVersion())
|
||||
.post(body)
|
||||
.build()
|
||||
OkHttp.client.newCall(request).executeAsync().use { response ->
|
||||
if (response.code >= 400) {
|
||||
call.reject(response.body.string())
|
||||
return@launch
|
||||
}
|
||||
CookieStore.getCookie(endpoint, "affine_session")?.let {
|
||||
call.resolve(JSObject().put("token", it))
|
||||
} ?: call.reject("token not found")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.reject("Failed to sign in, $e", null, e)
|
||||
}
|
||||
}
|
||||
processSignIn(call, SignInMethod.MagicLink)
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun signInOauth(call: PluginCall) {
|
||||
launch(Dispatchers.IO) {
|
||||
try {
|
||||
val endpoint = call.getStringEnsure("endpoint")
|
||||
val code = call.getStringEnsure("code")
|
||||
val state = call.getStringEnsure("state")
|
||||
val clientNonce = call.getString("clientNonce")
|
||||
val body = JSONObject()
|
||||
.apply {
|
||||
put("code", code)
|
||||
put("state", state)
|
||||
put("client_nonce", clientNonce)
|
||||
}
|
||||
.toString()
|
||||
.toRequestBody("application/json".toMediaTypeOrNull())
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("$endpoint/api/oauth/callback")
|
||||
.header("x-affine-version", CapacitorConfig.getAffineVersion())
|
||||
.post(body)
|
||||
.build()
|
||||
OkHttp.client.newCall(request).executeAsync().use { response ->
|
||||
if (response.code >= 400) {
|
||||
call.reject(response.body.string())
|
||||
return@launch
|
||||
}
|
||||
CookieStore.getCookie(endpoint, "affine_session")?.let {
|
||||
call.resolve(JSObject().put("token", it))
|
||||
} ?: call.reject("token not found")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.reject("Failed to sign in, $e", null, e)
|
||||
}
|
||||
}
|
||||
processSignIn(call, SignInMethod.Oauth)
|
||||
}
|
||||
|
||||
@SuppressLint("BuildListAdds")
|
||||
@PluginMethod
|
||||
fun signInPassword(call: PluginCall) {
|
||||
launch(Dispatchers.IO) {
|
||||
try {
|
||||
val endpoint = call.getStringEnsure("endpoint")
|
||||
val email = call.getStringEnsure("email")
|
||||
val password = call.getStringEnsure("password")
|
||||
val verifyToken = call.getString("verifyToken")
|
||||
val challenge = call.getString("challenge")
|
||||
val body = JSONObject()
|
||||
.apply {
|
||||
put("email", email)
|
||||
put("password", password)
|
||||
}
|
||||
.toString()
|
||||
.toRequestBody("application/json".toMediaTypeOrNull())
|
||||
|
||||
|
||||
val requestBuilder = Request.Builder()
|
||||
.url("$endpoint/api/auth/sign-in")
|
||||
.header("x-affine-version", CapacitorConfig.getAffineVersion())
|
||||
.post(body)
|
||||
if (verifyToken != null) {
|
||||
requestBuilder.addHeader("x-captcha-token", verifyToken)
|
||||
}
|
||||
if (challenge != null) {
|
||||
requestBuilder.addHeader("x-captcha-challenge", challenge)
|
||||
}
|
||||
OkHttp.client.newCall(requestBuilder.build()).executeAsync().use { response ->
|
||||
if (response.code >= 400) {
|
||||
call.reject(response.body.string())
|
||||
return@launch
|
||||
}
|
||||
CookieStore.getCookie(endpoint, "affine_session")?.let {
|
||||
call.resolve(JSObject().put("token", it))
|
||||
} ?: call.reject("token not found")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
call.reject("Failed to sign in, $e", null, e)
|
||||
}
|
||||
}
|
||||
processSignIn(call, SignInMethod.Password)
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
@@ -154,11 +56,110 @@ class AuthPlugin : Plugin() {
|
||||
call.reject(response.body.string())
|
||||
return@launch
|
||||
}
|
||||
Timber.i("Sign out success.")
|
||||
call.resolve(JSObject().put("ok", true))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e, "Sign out fail.")
|
||||
call.reject("Failed to sign out, $e", null, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum class SignInMethod {
|
||||
Password, Oauth, MagicLink
|
||||
}
|
||||
|
||||
private fun processSignIn(call: PluginCall, method: SignInMethod) {
|
||||
launch(Dispatchers.IO) {
|
||||
try {
|
||||
val endpoint = call.getStringEnsure("endpoint")
|
||||
val request = when (method) {
|
||||
SignInMethod.Password -> {
|
||||
val email = call.getStringEnsure("email")
|
||||
val password = call.getStringEnsure("password")
|
||||
val verifyToken = call.getString("verifyToken")
|
||||
val challenge = call.getString("challenge")
|
||||
val body = JSONObject()
|
||||
.apply {
|
||||
put("email", email)
|
||||
put("password", password)
|
||||
}
|
||||
.toString()
|
||||
.toRequestBody("application/json".toMediaTypeOrNull())
|
||||
|
||||
val requestBuilder = Request.Builder()
|
||||
.url("$endpoint/api/auth/sign-in")
|
||||
.header("x-affine-version", CapacitorConfig.getAffineVersion())
|
||||
.post(body)
|
||||
if (verifyToken != null) {
|
||||
requestBuilder.addHeader("x-captcha-token", verifyToken)
|
||||
}
|
||||
if (challenge != null) {
|
||||
requestBuilder.addHeader("x-captcha-challenge", challenge)
|
||||
}
|
||||
requestBuilder.build()
|
||||
}
|
||||
|
||||
SignInMethod.Oauth -> {
|
||||
val code = call.getStringEnsure("code")
|
||||
val state = call.getStringEnsure("state")
|
||||
val clientNonce = call.getString("clientNonce")
|
||||
val body = JSONObject()
|
||||
.apply {
|
||||
put("code", code)
|
||||
put("state", state)
|
||||
put("client_nonce", clientNonce)
|
||||
}
|
||||
.toString()
|
||||
.toRequestBody("application/json".toMediaTypeOrNull())
|
||||
|
||||
Request.Builder()
|
||||
.url("$endpoint/api/oauth/callback")
|
||||
.header("x-affine-version", CapacitorConfig.getAffineVersion())
|
||||
.post(body)
|
||||
.build()
|
||||
}
|
||||
|
||||
SignInMethod.MagicLink -> {
|
||||
val email = call.getStringEnsure("email")
|
||||
val token = call.getStringEnsure("token")
|
||||
val clientNonce = call.getString("clientNonce")
|
||||
val body = JSONObject()
|
||||
.apply {
|
||||
put("email", email)
|
||||
put("token", token)
|
||||
put("client_nonce", clientNonce)
|
||||
}
|
||||
.toString()
|
||||
.toRequestBody("application/json".toMediaTypeOrNull())
|
||||
|
||||
Request.Builder()
|
||||
.url("$endpoint/api/auth/magic-link")
|
||||
.header("x-affine-version", CapacitorConfig.getAffineVersion())
|
||||
.post(body)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
OkHttp.client.newCall(request).executeAsync().use { response ->
|
||||
if (response.code >= 400) {
|
||||
call.reject(response.body.string())
|
||||
return@launch
|
||||
}
|
||||
CookieStore.getCookie(endpoint, CookieStore.AFFINE_SESSION)?.let {
|
||||
Timber.i("$method sign in success.")
|
||||
Timber.d("Update session [$it]")
|
||||
call.resolve(JSObject().put("token", it))
|
||||
} ?: run {
|
||||
Timber.w("$method sign in fail, token not found.")
|
||||
call.reject("$method sign in fail, token not found")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e, "$method sign in fail.")
|
||||
call.reject("$method sign in fail.", null, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.getcapacitor.PluginCall
|
||||
import com.getcapacitor.PluginMethod
|
||||
import com.getcapacitor.annotation.CapacitorPlugin
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import timber.log.Timber
|
||||
import uniffi.affine_mobile_native.hashcashMint
|
||||
|
||||
@CapacitorPlugin(name = "HashCash")
|
||||
@@ -16,8 +17,10 @@ class HashCashPlugin : Plugin() {
|
||||
launch(Dispatchers.IO) {
|
||||
val challenge = call.getString("challenge") ?: ""
|
||||
val bits = call.getInt("bits") ?: 20
|
||||
val hash = hashcashMint(resource = challenge, bits = bits.toUInt())
|
||||
Timber.i("hash:[ value = $hash ]")
|
||||
call.resolve(JSObject().apply {
|
||||
put("value", hashcashMint(resource = challenge, bits = bits.toUInt()))
|
||||
put("value", hash)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ class NbStorePlugin : Plugin() {
|
||||
val spaceType = call.getStringEnsure("spaceType")
|
||||
val peer = call.getStringEnsure("peer")
|
||||
val appStoragePath = activity?.filesDir ?: run {
|
||||
call.reject("Failed to connect storage, cannot access file system.")
|
||||
Timber.w("Failed to connect storage, cannot access device file system.")
|
||||
call.reject("Failed to connect storage, cannot access device file system.")
|
||||
return@launch
|
||||
}
|
||||
val peerDir = appStoragePath.resolve("workspaces")
|
||||
@@ -38,13 +39,15 @@ class NbStorePlugin : Plugin() {
|
||||
.replace(Regex("_+"), "_")
|
||||
.replace(Regex("_+$"), "")
|
||||
)
|
||||
Timber.d("connecting nbstore... peerDir[$peerDir]")
|
||||
Timber.i("NbStore connecting... peerDir[$peerDir].")
|
||||
peerDir.mkdirs()
|
||||
val db = peerDir.resolve("$spaceId.db")
|
||||
docStoragePool.connect(id, db.path)
|
||||
Timber.i("NbStore connected [ id = $id ].")
|
||||
call.resolve()
|
||||
} catch (e: Exception) {
|
||||
call.reject("Failed to connect storage", e)
|
||||
Timber.e(e, "Failed to connect NbStore.")
|
||||
call.reject("Failed to connect NbStore.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,9 +58,11 @@ class NbStorePlugin : Plugin() {
|
||||
try {
|
||||
val id = call.getStringEnsure("id")
|
||||
docStoragePool.disconnect(universalId = id)
|
||||
Timber.i("NbStore disconnected [ id = $id ].")
|
||||
call.resolve()
|
||||
} catch (e: Exception) {
|
||||
call.reject("Failed to disconnect, ${e.message}", null, e)
|
||||
Timber.e(e, "Failed to disconnect NbStore")
|
||||
call.reject("Failed to disconnect NbStore", null, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,8 +74,10 @@ class NbStorePlugin : Plugin() {
|
||||
val id = call.getStringEnsure("id")
|
||||
val spaceId = call.getStringEnsure("spaceId")
|
||||
docStoragePool.setSpaceId(universalId = id, spaceId = spaceId)
|
||||
Timber.i("Set space id: [ id = $id, spaceId = $spaceId ].")
|
||||
call.resolve()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to set space id.")
|
||||
call.reject("Failed to set space id, ${e.message}", null, e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package app.affine.pro.repo
|
||||
|
||||
import app.affine.pro.ai.Prompt
|
||||
import app.affine.pro.Prompt
|
||||
import app.affine.pro.service.GraphQLClient
|
||||
import com.affine.pro.graphql.CreateCopilotMessageMutation
|
||||
import com.affine.pro.graphql.CreateCopilotSessionMutation
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
package app.affine.pro.service
|
||||
|
||||
import androidx.core.net.toUri
|
||||
import app.affine.pro.AffineApp
|
||||
import app.affine.pro.utils.dataStore
|
||||
import app.affine.pro.utils.set
|
||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.CookieJar
|
||||
import okhttp3.HttpUrl
|
||||
@@ -26,7 +34,6 @@ object OkHttp {
|
||||
}
|
||||
})
|
||||
.addInterceptor(HttpLoggingInterceptor { msg ->
|
||||
Timber.tag("Affine-Network")
|
||||
Timber.d(msg)
|
||||
}.apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
@@ -37,10 +44,23 @@ object OkHttp {
|
||||
|
||||
object CookieStore {
|
||||
|
||||
const val AFFINE_SESSION = "affine_session"
|
||||
const val AFFINE_USER_ID = "affine_user_id"
|
||||
|
||||
private val _cookies = ConcurrentHashMap<String, List<Cookie>>()
|
||||
|
||||
fun saveCookies(host: String, cookies: List<Cookie>) {
|
||||
_cookies[host] = cookies
|
||||
MainScope().launch(Dispatchers.IO) {
|
||||
cookies.find { it.name == AFFINE_SESSION }?.let {
|
||||
AffineApp.context().dataStore.set(AFFINE_SESSION, it.toString())
|
||||
}
|
||||
cookies.find { it.name == AFFINE_USER_ID }?.let {
|
||||
Timber.d("Update user id [${it.value}]")
|
||||
AffineApp.context().dataStore.set(AFFINE_USER_ID, it.toString())
|
||||
Firebase.crashlytics.setUserId(it.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getCookies(host: String) = _cookies[host] ?: emptyList()
|
||||
@@ -49,5 +69,4 @@ object CookieStore {
|
||||
?.let { _cookies[it] }
|
||||
?.find { cookie -> cookie.name == name }
|
||||
?.value
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package app.affine.pro.utils
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "affine")
|
||||
|
||||
suspend fun DataStore<Preferences>.set(key: String, value: String) {
|
||||
edit {
|
||||
it[stringPreferencesKey(key)] = value
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun DataStore<Preferences>.get(key: String) = data.map {
|
||||
it[stringPreferencesKey(key)] ?: ""
|
||||
}.first()
|
||||
|
||||
suspend fun DataStore<Preferences>.clear(vararg keys: String) {
|
||||
edit { prefs ->
|
||||
keys.forEach { key ->
|
||||
prefs[stringPreferencesKey(key)] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package app.affine.pro.utils.logger
|
||||
|
||||
import timber.log.Timber
|
||||
|
||||
class AffineDebugTree : Timber.DebugTree() {
|
||||
|
||||
override fun createStackElementTag(element: StackTraceElement): String {
|
||||
return "Affine:${super.createStackElementTag(element)}:${element.lineNumber}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package app.affine.pro.utils.logger
|
||||
|
||||
import android.util.Log
|
||||
import com.google.firebase.crashlytics.ktx.crashlytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import timber.log.Timber
|
||||
|
||||
class CrashlyticsTree : Timber.Tree() {
|
||||
|
||||
private val crashlytics = Firebase.crashlytics
|
||||
|
||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||
|
||||
if (priority < Log.INFO) {
|
||||
return
|
||||
}
|
||||
|
||||
val level = when (priority) {
|
||||
Log.ASSERT -> "[assert]"
|
||||
Log.ERROR -> "[error]"
|
||||
Log.WARN -> "[warn]"
|
||||
else -> "[info]"
|
||||
}
|
||||
|
||||
crashlytics.log(
|
||||
StringBuilder(level)
|
||||
.append(tag?.let { "[$tag]" } ?: "")
|
||||
.append(" ")
|
||||
.append(message)
|
||||
.toString()
|
||||
)
|
||||
|
||||
if (t == null) return
|
||||
crashlytics.recordException(t)
|
||||
}
|
||||
}
|
||||
@@ -21,15 +21,17 @@ plugins {
|
||||
alias libs.plugins.version.catalog.update
|
||||
alias libs.plugins.android.application apply false
|
||||
alias libs.plugins.android.library apply false
|
||||
alias libs.plugins.apollo.android apply false
|
||||
alias libs.plugins.compose apply false
|
||||
alias libs.plugins.firebase.crashlytics apply false
|
||||
alias libs.plugins.google.service apply false
|
||||
alias libs.plugins.hilt apply false
|
||||
alias libs.plugins.jetbrains.kotlin.jvm apply false
|
||||
alias libs.plugins.kotlin.android apply false
|
||||
alias libs.plugins.kotlin.parcelize apply false
|
||||
alias libs.plugins.kotlin.serialization apply false
|
||||
alias libs.plugins.compose apply false
|
||||
alias libs.plugins.ksp apply false
|
||||
alias libs.plugins.hilt apply false
|
||||
alias libs.plugins.rust.android apply false
|
||||
alias libs.plugins.apollo.android apply false
|
||||
alias libs.plugins.jetbrains.kotlin.jvm apply false
|
||||
}
|
||||
|
||||
apply from: "variables.gradle"
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
versionCatalogUpdate {
|
||||
sortByKey.set(true)
|
||||
|
||||
keep {
|
||||
// keep versions without any library or plugin reference
|
||||
keepUnusedVersions.set(true)
|
||||
// keep all libraries that aren't used in the project
|
||||
keepUnusedLibraries.set(true)
|
||||
// keep all plugins that aren't used in the project
|
||||
keepUnusedPlugins.set(true)
|
||||
sortByKey = true
|
||||
versionCatalogs {
|
||||
special {
|
||||
keep {
|
||||
keepUnusedVersions = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
[versions]
|
||||
# @keep
|
||||
android-gradle-plugin = "8.9.1"
|
||||
androidx-activity-compose = "1.10.1"
|
||||
androidx-appcompat = "1.7.0"
|
||||
@@ -8,6 +7,7 @@ androidx-compose-bom = "2025.04.00"
|
||||
androidx-coordinatorlayout = "1.3.0"
|
||||
androidx-core-ktx = "1.16.0"
|
||||
androidx-core-splashscreen = "1.0.1"
|
||||
androidx-datastore-preferences = "1.1.4"
|
||||
androidx-espresso-core = "3.6.1"
|
||||
androidx-junit = "1.2.1"
|
||||
androidx-lifecycle-compose = "2.8.7"
|
||||
@@ -17,6 +17,8 @@ apollo = "4.1.1"
|
||||
apollo-kotlin-adapters = "0.0.4"
|
||||
# @keep
|
||||
compileSdk = "35"
|
||||
firebase-bom = "33.12.0"
|
||||
firebase-crashlytics = "3.0.3"
|
||||
google-services = "4.4.2"
|
||||
gradle-versions = "0.52.0"
|
||||
hilt = "2.56.1"
|
||||
@@ -31,11 +33,11 @@ ksp = "2.1.20-2.0.0"
|
||||
# @keep
|
||||
minSdk = "22"
|
||||
mozilla-rust-android = "0.9.6"
|
||||
okhttp = "5.0.0-alpha.14"
|
||||
okhttp-bom = "5.0.0-alpha.14"
|
||||
# @keep
|
||||
targetSdk = "35"
|
||||
timber = "5.0.1"
|
||||
version-catalog-update = "0.8.5"
|
||||
version-catalog-update = "1.0.0"
|
||||
|
||||
[libraries]
|
||||
android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "android-gradle-plugin" }
|
||||
@@ -56,6 +58,7 @@ androidx-compose-ui-viewbinding = { module = "androidx.compose.ui:ui-viewbinding
|
||||
androidx-coordinatorlayout = { module = "androidx.coordinatorlayout:coordinatorlayout", version.ref = "androidx-coordinatorlayout" }
|
||||
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core-ktx" }
|
||||
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-core-splashscreen" }
|
||||
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore-preferences" }
|
||||
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso-core" }
|
||||
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" }
|
||||
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle-compose" }
|
||||
@@ -63,10 +66,13 @@ androidx-lifecycle-viewModelCompose = { module = "androidx.lifecycle:lifecycle-v
|
||||
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
|
||||
androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "androidx-navigation" }
|
||||
androidx-navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "androidx-navigation" }
|
||||
apollo-adapters-core = { module = "com.apollographql.adapters:apollo-adapters-core", version.ref = "apollo-kotlin-adapters"}
|
||||
apollo-adapters-kotlinx-datetime = { module = "com.apollographql.adapters:apollo-adapters-kotlinx-datetime", version.ref = "apollo-kotlin-adapters"}
|
||||
apollo-adapters-core = { module = "com.apollographql.adapters:apollo-adapters-core", version.ref = "apollo-kotlin-adapters" }
|
||||
apollo-adapters-kotlinx-datetime = { module = "com.apollographql.adapters:apollo-adapters-kotlinx-datetime", version.ref = "apollo-kotlin-adapters" }
|
||||
apollo-api = { module = "com.apollographql.apollo:apollo-api", version.ref = "apollo" }
|
||||
apollo-runtime = { module = "com.apollographql.apollo:apollo-runtime", version.ref = "apollo" }
|
||||
firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
|
||||
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" }
|
||||
firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" }
|
||||
google-services = { module = "com.google.gms:google-services", version.ref = "google-services" }
|
||||
hilt-android-core = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
|
||||
hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" }
|
||||
@@ -79,8 +85,8 @@ kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutine
|
||||
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
|
||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
|
||||
okhttp = { module = "com.squareup.okhttp3:okhttp" }
|
||||
okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttp-bom" }
|
||||
okhttp-coroutines = { module = "com.squareup.okhttp3:okhttp-coroutines" }
|
||||
okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttp" }
|
||||
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor" }
|
||||
okhttp-sse = { module = "com.squareup.okhttp3:okhttp-sse" }
|
||||
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
|
||||
@@ -90,6 +96,8 @@ android-application = { id = "com.android.application", version.ref = "android-g
|
||||
android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" }
|
||||
apollo-android = { id = "com.apollographql.apollo", version.ref = "apollo" }
|
||||
compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics" }
|
||||
google-service = { id = "com.google.gms.google-services", version.ref = "google-services" }
|
||||
gradle-versions = { id = "com.github.ben-manes.versions", version.ref = "gradle-versions" }
|
||||
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
||||
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
|
||||
Reference in New Issue
Block a user