mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-21 20:16:26 +08:00
chore(android): integrate firebase-crashlytics (#11808)
This commit is contained in:
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
package app.affine.pro.ai
|
||||
package app.affine.pro
|
||||
|
||||
enum class Prompt(val value: String) {
|
||||
Summary("Summary"),
|
||||
+5
-10
@@ -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.")
|
||||
|
||||
+3
@@ -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
-1
@@ -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()
|
||||
}
|
||||
}
|
||||
+106
-105
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+11
-4
@@ -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
-1
@@ -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
|
||||
|
||||
+21
-2
@@ -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
|
||||
|
||||
}
|
||||
+30
@@ -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)] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -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}"
|
||||
}
|
||||
}
|
||||
+36
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user