mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
chore(android): integrate firebase-crashlytics (#11808)
This commit is contained in:
4
.github/workflows/release-mobile.yml
vendored
4
.github/workflows/release-mobile.yml
vendored
@@ -205,6 +205,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: android
|
name: android
|
||||||
path: packages/frontend/apps/android/dist
|
path: packages/frontend/apps/android/dist
|
||||||
|
- name: Load Google Service file
|
||||||
|
env:
|
||||||
|
DATA: ${{ secrets.FIREBASE_ANDROID_GOOGLE_SERVICE_JSON }}
|
||||||
|
run: echo $DATA | base64 -di > packages/frontend/apps/android/App/app/google-services.json
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: ./.github/actions/setup-node
|
uses: ./.github/actions/setup-node
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
/build/*
|
/build/*
|
||||||
!/build/.npmkeep
|
!/build/.npmkeep
|
||||||
|
google-services.json
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias libs.plugins.android.application
|
alias libs.plugins.android.application
|
||||||
alias libs.plugins.compose
|
|
||||||
alias libs.plugins.hilt
|
|
||||||
alias libs.plugins.kotlin.android
|
alias libs.plugins.kotlin.android
|
||||||
alias libs.plugins.kotlin.parcelize
|
alias libs.plugins.kotlin.parcelize
|
||||||
alias libs.plugins.kotlin.serialization
|
alias libs.plugins.kotlin.serialization
|
||||||
alias libs.plugins.ksp
|
alias libs.plugins.ksp
|
||||||
alias libs.plugins.rust.android
|
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'
|
apply from: 'capacitor.build.gradle'
|
||||||
@@ -106,6 +108,7 @@ dependencies {
|
|||||||
implementation libs.androidx.coordinatorlayout
|
implementation libs.androidx.coordinatorlayout
|
||||||
implementation libs.androidx.core.ktx
|
implementation libs.androidx.core.ktx
|
||||||
implementation libs.androidx.core.splashscreen
|
implementation libs.androidx.core.splashscreen
|
||||||
|
implementation libs.androidx.datastore.preferences
|
||||||
implementation libs.androidx.navigation.fragment
|
implementation libs.androidx.navigation.fragment
|
||||||
implementation libs.androidx.navigation.ui.ktx
|
implementation libs.androidx.navigation.ui.ktx
|
||||||
|
|
||||||
@@ -118,8 +121,11 @@ dependencies {
|
|||||||
implementation libs.kotlinx.coroutines.android
|
implementation libs.kotlinx.coroutines.android
|
||||||
implementation libs.kotlinx.serialization.json
|
implementation libs.kotlinx.serialization.json
|
||||||
|
|
||||||
def okhttpBom = platform(libs.okhttp.bom)
|
implementation platform(libs.firebase.bom)
|
||||||
implementation okhttpBom
|
implementation libs.firebase.analytics
|
||||||
|
implementation libs.firebase.crashlytics
|
||||||
|
|
||||||
|
implementation platform(libs.okhttp.bom)
|
||||||
implementation libs.okhttp
|
implementation libs.okhttp
|
||||||
implementation libs.okhttp.coroutines
|
implementation libs.okhttp.coroutines
|
||||||
implementation libs.okhttp.logging
|
implementation libs.okhttp.logging
|
||||||
@@ -130,15 +136,6 @@ dependencies {
|
|||||||
androidTestImplementation libs.androidx.espresso.core
|
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 {
|
cargo {
|
||||||
module = "../../../../mobile-native"
|
module = "../../../../mobile-native"
|
||||||
libname = "affine_mobile_native"
|
libname = "affine_mobile_native"
|
||||||
|
|||||||
@@ -68,3 +68,8 @@
|
|||||||
-keepclassmembers public class **$$serializer {
|
-keepclassmembers public class **$$serializer {
|
||||||
private ** descriptor;
|
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
|
package app.affine.pro
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Application
|
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 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
|
import timber.log.Timber
|
||||||
|
|
||||||
@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
@@ -9,8 +24,47 @@ class AffineApp : Application() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.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)
|
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) {
|
enum class Prompt(val value: String) {
|
||||||
Summary("Summary"),
|
Summary("Summary"),
|
||||||
@@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.coroutines.coroutineContext
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ChatViewModel @Inject constructor(
|
class ChatViewModel @Inject constructor(
|
||||||
@@ -37,10 +36,10 @@ class ChatViewModel @Inject constructor(
|
|||||||
workspaceId = webRepo.workspaceId(),
|
workspaceId = webRepo.workspaceId(),
|
||||||
docId = webRepo.docId(),
|
docId = webRepo.docId(),
|
||||||
).getOrElse {
|
).getOrElse {
|
||||||
Timber.d("Create session failed")
|
Timber.w(it, "Create session failed")
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
Timber.d("Create session: $sessionId")
|
Timber.i("Create session success:[ sessionId = $sessionId].")
|
||||||
val historyMessages = graphQLRepo.getCopilotHistories(
|
val historyMessages = graphQLRepo.getCopilotHistories(
|
||||||
workspaceId = webRepo.workspaceId(),
|
workspaceId = webRepo.workspaceId(),
|
||||||
docId = webRepo.docId(),
|
docId = webRepo.docId(),
|
||||||
@@ -60,16 +59,12 @@ class ChatViewModel @Inject constructor(
|
|||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
message = message,
|
message = message,
|
||||||
).onSuccess { messageId ->
|
).onSuccess { messageId ->
|
||||||
Timber.d("send message: $messageId")
|
Timber.i("send message: $messageId")
|
||||||
sseRepo.messageStream(sessionId, messageId)
|
sseRepo.messageStream(sessionId, messageId)
|
||||||
.onEach {
|
.onEach {
|
||||||
Timber.d("$coroutineContext")
|
Timber.d("On sse message: ${it.getOrNull()}")
|
||||||
Timber.d("on message: ${it.getOrNull()}")
|
|
||||||
}
|
}
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
.onEach {
|
|
||||||
Timber.d("$coroutineContext")
|
|
||||||
}
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,7 +75,7 @@ class ChatViewModel @Inject constructor(
|
|||||||
docId = webRepo.docId(),
|
docId = webRepo.docId(),
|
||||||
).onSuccess { id ->
|
).onSuccess { id ->
|
||||||
sessionId = id
|
sessionId = id
|
||||||
Timber.d("Create session: $id")
|
Timber.i("Create session: $id")
|
||||||
sendMessage()
|
sendMessage()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.e(it, "Create session failed.")
|
Timber.e(it, "Create session failed.")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.getcapacitor.Plugin
|
|||||||
import com.getcapacitor.PluginCall
|
import com.getcapacitor.PluginCall
|
||||||
import com.getcapacitor.PluginMethod
|
import com.getcapacitor.PluginMethod
|
||||||
import com.getcapacitor.annotation.CapacitorPlugin
|
import com.getcapacitor.annotation.CapacitorPlugin
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
@CapacitorPlugin(name = "AIButton")
|
@CapacitorPlugin(name = "AIButton")
|
||||||
class AIButtonPlugin : Plugin() {
|
class AIButtonPlugin : Plugin() {
|
||||||
@@ -16,6 +17,7 @@ class AIButtonPlugin : Plugin() {
|
|||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun present(call: PluginCall) {
|
fun present(call: PluginCall) {
|
||||||
launch {
|
launch {
|
||||||
|
Timber.i("present AIButton")
|
||||||
(activity as? Callback)?.present()
|
(activity as? Callback)?.present()
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
@@ -24,6 +26,7 @@ class AIButtonPlugin : Plugin() {
|
|||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun dismiss(call: PluginCall) {
|
fun dismiss(call: PluginCall) {
|
||||||
launch {
|
launch {
|
||||||
|
Timber.i("dismiss AIButton")
|
||||||
(activity as? Callback)?.dismiss()
|
(activity as? Callback)?.dismiss()
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.getcapacitor.Plugin
|
|||||||
import com.getcapacitor.PluginCall
|
import com.getcapacitor.PluginCall
|
||||||
import com.getcapacitor.PluginMethod
|
import com.getcapacitor.PluginMethod
|
||||||
import com.getcapacitor.annotation.CapacitorPlugin
|
import com.getcapacitor.annotation.CapacitorPlugin
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
@CapacitorPlugin(name = "AffineTheme")
|
@CapacitorPlugin(name = "AffineTheme")
|
||||||
class AffineThemePlugin : Plugin() {
|
class AffineThemePlugin : Plugin() {
|
||||||
@@ -14,7 +15,9 @@ class AffineThemePlugin : Plugin() {
|
|||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun onThemeChanged(call: PluginCall) {
|
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()
|
call.resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
package app.affine.pro.plugin
|
package app.affine.pro.plugin
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import app.affine.pro.AffineApp
|
||||||
import app.affine.pro.CapacitorConfig
|
import app.affine.pro.CapacitorConfig
|
||||||
import app.affine.pro.service.CookieStore
|
import app.affine.pro.service.CookieStore
|
||||||
import app.affine.pro.service.OkHttp
|
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.JSObject
|
||||||
import com.getcapacitor.Plugin
|
import com.getcapacitor.Plugin
|
||||||
import com.getcapacitor.PluginCall
|
import com.getcapacitor.PluginCall
|
||||||
@@ -16,6 +19,7 @@ import okhttp3.Request
|
|||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.coroutines.executeAsync
|
import okhttp3.coroutines.executeAsync
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@CapacitorPlugin(name = "Auth")
|
@CapacitorPlugin(name = "Auth")
|
||||||
@@ -23,120 +27,18 @@ class AuthPlugin : Plugin() {
|
|||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun signInMagicLink(call: PluginCall) {
|
fun signInMagicLink(call: PluginCall) {
|
||||||
launch(Dispatchers.IO) {
|
processSignIn(call, SignInMethod.MagicLink)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun signInOauth(call: PluginCall) {
|
fun signInOauth(call: PluginCall) {
|
||||||
launch(Dispatchers.IO) {
|
processSignIn(call, SignInMethod.Oauth)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("BuildListAdds")
|
@SuppressLint("BuildListAdds")
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun signInPassword(call: PluginCall) {
|
fun signInPassword(call: PluginCall) {
|
||||||
launch(Dispatchers.IO) {
|
processSignIn(call, SignInMethod.Password)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
@@ -154,11 +56,110 @@ class AuthPlugin : Plugin() {
|
|||||||
call.reject(response.body.string())
|
call.reject(response.body.string())
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
Timber.i("Sign out success.")
|
||||||
call.resolve(JSObject().put("ok", true))
|
call.resolve(JSObject().put("ok", true))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Timber.w(e, "Sign out fail.")
|
||||||
call.reject("Failed to sign out, $e", null, e)
|
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.PluginMethod
|
||||||
import com.getcapacitor.annotation.CapacitorPlugin
|
import com.getcapacitor.annotation.CapacitorPlugin
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import timber.log.Timber
|
||||||
import uniffi.affine_mobile_native.hashcashMint
|
import uniffi.affine_mobile_native.hashcashMint
|
||||||
|
|
||||||
@CapacitorPlugin(name = "HashCash")
|
@CapacitorPlugin(name = "HashCash")
|
||||||
@@ -16,8 +17,10 @@ class HashCashPlugin : Plugin() {
|
|||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
val challenge = call.getString("challenge") ?: ""
|
val challenge = call.getString("challenge") ?: ""
|
||||||
val bits = call.getInt("bits") ?: 20
|
val bits = call.getInt("bits") ?: 20
|
||||||
|
val hash = hashcashMint(resource = challenge, bits = bits.toUInt())
|
||||||
|
Timber.i("hash:[ value = $hash ]")
|
||||||
call.resolve(JSObject().apply {
|
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 spaceType = call.getStringEnsure("spaceType")
|
||||||
val peer = call.getStringEnsure("peer")
|
val peer = call.getStringEnsure("peer")
|
||||||
val appStoragePath = activity?.filesDir ?: run {
|
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
|
return@launch
|
||||||
}
|
}
|
||||||
val peerDir = appStoragePath.resolve("workspaces")
|
val peerDir = appStoragePath.resolve("workspaces")
|
||||||
@@ -38,13 +39,15 @@ class NbStorePlugin : Plugin() {
|
|||||||
.replace(Regex("_+"), "_")
|
.replace(Regex("_+"), "_")
|
||||||
.replace(Regex("_+$"), "")
|
.replace(Regex("_+$"), "")
|
||||||
)
|
)
|
||||||
Timber.d("connecting nbstore... peerDir[$peerDir]")
|
Timber.i("NbStore connecting... peerDir[$peerDir].")
|
||||||
peerDir.mkdirs()
|
peerDir.mkdirs()
|
||||||
val db = peerDir.resolve("$spaceId.db")
|
val db = peerDir.resolve("$spaceId.db")
|
||||||
docStoragePool.connect(id, db.path)
|
docStoragePool.connect(id, db.path)
|
||||||
|
Timber.i("NbStore connected [ id = $id ].")
|
||||||
call.resolve()
|
call.resolve()
|
||||||
} catch (e: Exception) {
|
} 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 {
|
try {
|
||||||
val id = call.getStringEnsure("id")
|
val id = call.getStringEnsure("id")
|
||||||
docStoragePool.disconnect(universalId = id)
|
docStoragePool.disconnect(universalId = id)
|
||||||
|
Timber.i("NbStore disconnected [ id = $id ].")
|
||||||
call.resolve()
|
call.resolve()
|
||||||
} catch (e: Exception) {
|
} 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 id = call.getStringEnsure("id")
|
||||||
val spaceId = call.getStringEnsure("spaceId")
|
val spaceId = call.getStringEnsure("spaceId")
|
||||||
docStoragePool.setSpaceId(universalId = id, spaceId = spaceId)
|
docStoragePool.setSpaceId(universalId = id, spaceId = spaceId)
|
||||||
|
Timber.i("Set space id: [ id = $id, spaceId = $spaceId ].")
|
||||||
call.resolve()
|
call.resolve()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "Failed to set space id.")
|
||||||
call.reject("Failed to set space id, ${e.message}", null, e)
|
call.reject("Failed to set space id, ${e.message}", null, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package app.affine.pro.repo
|
package app.affine.pro.repo
|
||||||
|
|
||||||
import app.affine.pro.ai.Prompt
|
import app.affine.pro.Prompt
|
||||||
import app.affine.pro.service.GraphQLClient
|
import app.affine.pro.service.GraphQLClient
|
||||||
import com.affine.pro.graphql.CreateCopilotMessageMutation
|
import com.affine.pro.graphql.CreateCopilotMessageMutation
|
||||||
import com.affine.pro.graphql.CreateCopilotSessionMutation
|
import com.affine.pro.graphql.CreateCopilotSessionMutation
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
package app.affine.pro.service
|
package app.affine.pro.service
|
||||||
|
|
||||||
import androidx.core.net.toUri
|
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.Cookie
|
||||||
import okhttp3.CookieJar
|
import okhttp3.CookieJar
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
@@ -26,7 +34,6 @@ object OkHttp {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.addInterceptor(HttpLoggingInterceptor { msg ->
|
.addInterceptor(HttpLoggingInterceptor { msg ->
|
||||||
Timber.tag("Affine-Network")
|
|
||||||
Timber.d(msg)
|
Timber.d(msg)
|
||||||
}.apply {
|
}.apply {
|
||||||
level = HttpLoggingInterceptor.Level.BODY
|
level = HttpLoggingInterceptor.Level.BODY
|
||||||
@@ -37,10 +44,23 @@ object OkHttp {
|
|||||||
|
|
||||||
object CookieStore {
|
object CookieStore {
|
||||||
|
|
||||||
|
const val AFFINE_SESSION = "affine_session"
|
||||||
|
const val AFFINE_USER_ID = "affine_user_id"
|
||||||
|
|
||||||
private val _cookies = ConcurrentHashMap<String, List<Cookie>>()
|
private val _cookies = ConcurrentHashMap<String, List<Cookie>>()
|
||||||
|
|
||||||
fun saveCookies(host: String, cookies: List<Cookie>) {
|
fun saveCookies(host: String, cookies: List<Cookie>) {
|
||||||
_cookies[host] = cookies
|
_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()
|
fun getCookies(host: String) = _cookies[host] ?: emptyList()
|
||||||
@@ -49,5 +69,4 @@ object CookieStore {
|
|||||||
?.let { _cookies[it] }
|
?.let { _cookies[it] }
|
||||||
?.find { cookie -> cookie.name == name }
|
?.find { cookie -> cookie.name == name }
|
||||||
?.value
|
?.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.version.catalog.update
|
||||||
alias libs.plugins.android.application apply false
|
alias libs.plugins.android.application apply false
|
||||||
alias libs.plugins.android.library 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.android apply false
|
||||||
alias libs.plugins.kotlin.parcelize apply false
|
alias libs.plugins.kotlin.parcelize apply false
|
||||||
alias libs.plugins.kotlin.serialization apply false
|
alias libs.plugins.kotlin.serialization apply false
|
||||||
alias libs.plugins.compose apply false
|
|
||||||
alias libs.plugins.ksp apply false
|
alias libs.plugins.ksp apply false
|
||||||
alias libs.plugins.hilt apply false
|
|
||||||
alias libs.plugins.rust.android 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"
|
apply from: "variables.gradle"
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
versionCatalogUpdate {
|
versionCatalogUpdate {
|
||||||
sortByKey.set(true)
|
sortByKey = true
|
||||||
|
versionCatalogs {
|
||||||
keep {
|
special {
|
||||||
// keep versions without any library or plugin reference
|
keep {
|
||||||
keepUnusedVersions.set(true)
|
keepUnusedVersions = 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
[versions]
|
[versions]
|
||||||
# @keep
|
|
||||||
android-gradle-plugin = "8.9.1"
|
android-gradle-plugin = "8.9.1"
|
||||||
androidx-activity-compose = "1.10.1"
|
androidx-activity-compose = "1.10.1"
|
||||||
androidx-appcompat = "1.7.0"
|
androidx-appcompat = "1.7.0"
|
||||||
@@ -8,6 +7,7 @@ androidx-compose-bom = "2025.04.00"
|
|||||||
androidx-coordinatorlayout = "1.3.0"
|
androidx-coordinatorlayout = "1.3.0"
|
||||||
androidx-core-ktx = "1.16.0"
|
androidx-core-ktx = "1.16.0"
|
||||||
androidx-core-splashscreen = "1.0.1"
|
androidx-core-splashscreen = "1.0.1"
|
||||||
|
androidx-datastore-preferences = "1.1.4"
|
||||||
androidx-espresso-core = "3.6.1"
|
androidx-espresso-core = "3.6.1"
|
||||||
androidx-junit = "1.2.1"
|
androidx-junit = "1.2.1"
|
||||||
androidx-lifecycle-compose = "2.8.7"
|
androidx-lifecycle-compose = "2.8.7"
|
||||||
@@ -17,6 +17,8 @@ apollo = "4.1.1"
|
|||||||
apollo-kotlin-adapters = "0.0.4"
|
apollo-kotlin-adapters = "0.0.4"
|
||||||
# @keep
|
# @keep
|
||||||
compileSdk = "35"
|
compileSdk = "35"
|
||||||
|
firebase-bom = "33.12.0"
|
||||||
|
firebase-crashlytics = "3.0.3"
|
||||||
google-services = "4.4.2"
|
google-services = "4.4.2"
|
||||||
gradle-versions = "0.52.0"
|
gradle-versions = "0.52.0"
|
||||||
hilt = "2.56.1"
|
hilt = "2.56.1"
|
||||||
@@ -31,11 +33,11 @@ ksp = "2.1.20-2.0.0"
|
|||||||
# @keep
|
# @keep
|
||||||
minSdk = "22"
|
minSdk = "22"
|
||||||
mozilla-rust-android = "0.9.6"
|
mozilla-rust-android = "0.9.6"
|
||||||
okhttp = "5.0.0-alpha.14"
|
okhttp-bom = "5.0.0-alpha.14"
|
||||||
# @keep
|
# @keep
|
||||||
targetSdk = "35"
|
targetSdk = "35"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
version-catalog-update = "0.8.5"
|
version-catalog-update = "1.0.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "android-gradle-plugin" }
|
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-coordinatorlayout = { module = "androidx.coordinatorlayout:coordinatorlayout", version.ref = "androidx-coordinatorlayout" }
|
||||||
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core-ktx" }
|
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-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-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-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" }
|
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-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-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" }
|
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-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-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-api = { module = "com.apollographql.apollo:apollo-api", version.ref = "apollo" }
|
||||||
apollo-runtime = { module = "com.apollographql.apollo:apollo-runtime", 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" }
|
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-core = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
|
||||||
hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", 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-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" }
|
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
|
||||||
okhttp = { module = "com.squareup.okhttp3:okhttp" }
|
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-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-logging = { module = "com.squareup.okhttp3:logging-interceptor" }
|
||||||
okhttp-sse = { module = "com.squareup.okhttp3:okhttp-sse" }
|
okhttp-sse = { module = "com.squareup.okhttp3:okhttp-sse" }
|
||||||
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
|
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" }
|
android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" }
|
||||||
apollo-android = { id = "com.apollographql.apollo", version.ref = "apollo" }
|
apollo-android = { id = "com.apollographql.apollo", version.ref = "apollo" }
|
||||||
compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
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" }
|
gradle-versions = { id = "com.github.ben-manes.versions", version.ref = "gradle-versions" }
|
||||||
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
||||||
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||||
|
|||||||
Reference in New Issue
Block a user