feat(android): sign-in with google & magic-link & email (#9868)

- [chore(android): migrate to version catalog](16c0fb66e7)
- [feat(android): integrate apollo](4dcf93b4f9)
- [fix(android): fix android email sign-in](752cf34f33)
- [chore(android): add stable/canary environment](72a96bfa5f)
- [feat(android): set cookies for apollo client](7664cc4f19)
- [feat(android): google & magic-link sign-in](c54ce3b43b)
- [eat(android): change logo](8c5062adbc)
- [chore(android): fix pipleline](4a68299be4)
- [fix(android): rebase issues](c6858c5ecf)
- [docs(android): update README for compat with java 21](6eac3ba0dc)
- [fix(android): android pipeline](1103c87880)
This commit is contained in:
aki-chang-dev
2025-02-05 06:08:27 +00:00
parent cbb73d8034
commit 2607e34063
65 changed files with 2378 additions and 279 deletions

View File

@@ -231,7 +231,7 @@ jobs:
- name: Build - name: Build
run: | run: |
echo -n "${{ env.AFFINE_ANDROID_SIGN_KEYSTORE }}" | base64 --decode > packages/frontend/apps/android/affine.keystore echo -n "${{ env.AFFINE_ANDROID_SIGN_KEYSTORE }}" | base64 --decode > packages/frontend/apps/android/affine.keystore
yarn workspace @affine/android cap build android yarn workspace @affine/android cap build android --flavor ${{ env.BUILD_TYPE }} --androidreleasetype AAB
env: env:
AFFINE_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.AFFINE_ANDROID_KEYSTORE_PASSWORD }} AFFINE_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.AFFINE_ANDROID_KEYSTORE_PASSWORD }}
AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD: ${{ secrets.AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD }} AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD: ${{ secrets.AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD }}
@@ -243,7 +243,7 @@ jobs:
with: with:
serviceAccountJson: ${{ steps.auth.outputs.credentials_file_path }} serviceAccountJson: ${{ steps.auth.outputs.credentials_file_path }}
packageName: app.affine.pro packageName: app.affine.pro
releaseFiles: packages/frontend/apps/android/App/app/build/outputs/bundle/release/app-release-signed.aab releaseFiles: packages/frontend/apps/android/App/app/build/outputs/bundle/${{ env.BUILD_TYPE }}Release/app-${{ env.BUILD_TYPE }}-release-signed.aab
track: internal track: internal
status: draft status: draft
existingEditId: ${{ steps.bump.outputs.EDIT_ID }} existingEditId: ${{ steps.bump.outputs.EDIT_ID }}

View File

@@ -1,12 +1,16 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
apply plugin: 'com.android.application' plugins {
apply plugin: 'kotlin-android' alias libs.plugins.android.application
alias libs.plugins.kotlin.android
alias libs.plugins.rust.android
}
apply from: 'capacitor.build.gradle' apply from: 'capacitor.build.gradle'
android { android {
namespace "app.affine.pro" namespace "app.affine.pro"
compileSdk rootProject.ext.compileSdkVersion compileSdk rootProject.ext.compileSdkVersion
ndkVersion = new File(sdkDirectory, "ndk").listFiles().sort().last().name ndkVersion = new File(sdkDirectory, "ndk").listFiles().sort().last().name
defaultConfig { defaultConfig {
@@ -17,20 +21,35 @@ android {
versionName "1.0" versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions { aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61 // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
} }
ndk { ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64' abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
} }
} }
buildFeatures {
buildConfig true
}
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
flavorDimensions = ['chanel']
productFlavors {
stable {
buildConfigField 'String', 'BASE_URL', '"https://app.affine.pro"'
resValue 'string', 'host', '"app.affine.pro"'
}
canary {
buildConfigField 'String', 'BASE_URL', '"https://affine.fail"'
resValue 'string', 'host', '"affine.fail"'
}
}
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_21 sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21 targetCompatibility JavaVersion.VERSION_21
@@ -42,20 +61,22 @@ repositories {
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
} }
} }
dependencies { dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation project(':capacitor-android') implementation project(':capacitor-android')
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
implementation project(':capacitor-cordova-android-plugins') implementation project(':capacitor-cordova-android-plugins')
implementation "net.java.dev.jna:jna:5.16.0@aar" implementation project(':service')
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1" implementation libs.kotlinx.coroutines.core
implementation 'androidx.core:core-ktx:1.15.0' implementation libs.androidx.appcompat
implementation libs.androidx.browser
implementation libs.androidx.coordinatorlayout
implementation libs.androidx.core.splashscreen
implementation libs.androidx.core.ktx
implementation libs.apollo.runtime
implementation libs.jna
testImplementation libs.junit
androidTestImplementation libs.androidx.junit
androidTestImplementation libs.androidx.espresso.core
} }
try { try {
@@ -63,28 +84,26 @@ try {
if (servicesJSON.text) { if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
} }
} catch(Exception ignored) { } catch (Exception ignored) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work") logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
} }
apply plugin: 'org.mozilla.rust-android-gradle.rust-android'
cargo { cargo {
module = "../../../../mobile-native" module = "../../../../mobile-native"
libname = "affine_mobile_native" libname = "affine_mobile_native"
targets = ["arm64"] targets = ["arm64"]
pythonCommand = "python3.12" pythonCommand = "python3.12"
targetDirectory = "../../../../../../target" targetDirectory = "../../../../../../target"
apiLevel = 28 apiLevel = 28
targetIncludes = ["libaffine_mobile_native.so"] targetIncludes = ["libaffine_mobile_native.so"]
profile = "release" profile = "release"
} }
kotlin { kotlin {
compilerOptions { compilerOptions {
apiVersion = KotlinVersion.KOTLIN_2_0 apiVersion = KotlinVersion.KOTLIN_2_1
jvmTarget = JvmTarget.JVM_21 jvmTarget = JvmTarget.JVM_21
} }
} }
afterEvaluate { afterEvaluate {
@@ -103,7 +122,7 @@ android.applicationVariants.configureEach { variant ->
def t = tasks.register("generate${variant.name.capitalize()}UniFFIBindings", Exec) { def t = tasks.register("generate${variant.name.capitalize()}UniFFIBindings", Exec) {
workingDir "${project.projectDir}" workingDir "${project.projectDir}"
// Runs the bindings generation, note that you must have uniffi-bindgen installed and in your PATH environment variable // Runs the bindings generation, note that you must have uniffi-bindgen installed and in your PATH environment variable
commandLine 'cargo', 'run', '--bin', 'uniffi-bindgen', 'generate', '--library', "${buildDir}/rustJniLibs/android/arm64-v8a/libaffine_mobile_native.so", '--language', 'kotlin', '--out-dir', "${project.projectDir}/src/main/java" commandLine "${System.getenv("CARGO_HOME")}/bin/cargo", 'run', '--bin', 'uniffi-bindgen', 'generate', '--library', "${buildDir}/rustJniLibs/android/arm64-v8a/libaffine_mobile_native.so", '--language', 'kotlin', '--out-dir', "${project.projectDir}/src/main/java"
dependsOn("cargoBuild") dependsOn("cargoBuild")
} }
variant.javaCompileProvider.get().dependsOn(t) variant.javaCompileProvider.get().dependsOn(t)

View File

@@ -2,14 +2,15 @@
android { android {
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_17 sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_21
} }
} }
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies { dependencies {
implementation project(':capacitor-app')
implementation project(':capgo-inappbrowser')
} }

View File

@@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<queries>
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
@@ -22,6 +28,12 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="affine" android:host="authentication" android:pathPattern=".*"/>
</intent-filter>
</activity> </activity>
<provider <provider

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -2,14 +2,23 @@ package app.affine.pro
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.getcapacitor.BridgeActivity import com.getcapacitor.BridgeActivity
import uniffi.affine_mobile_native.hashcashMint; import com.getcapacitor.plugin.CapacitorCookies
import com.getcapacitor.plugin.CapacitorHttp
import ee.forgr.capacitor_inappbrowser.InAppBrowserPlugin
class MainActivity : BridgeActivity() { class MainActivity : BridgeActivity() {
@RequiresApi(Build.VERSION_CODES.R) @RequiresApi(Build.VERSION_CODES.R)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
registerPlugins(
listOf(
CapacitorHttp::class.java,
CapacitorCookies::class.java,
InAppBrowserPlugin::class.java
)
)
} }
} }

View File

@@ -0,0 +1,30 @@
package app.affine.pro.service
import app.affine.pro.BuildConfig
import app.affine.pro.service.interceptor.CookieInterceptor
import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.api.ApolloResponse
import com.apollographql.apollo.api.Mutation
import com.apollographql.apollo.api.Query
import com.apollographql.apollo.api.Subscription
object AffineClient {
private val _client: ApolloClient by lazy {
ApolloClient.Builder().serverUrl(BuildConfig.BASE_URL)
.addHttpInterceptor(CookieInterceptor)
.build()
}
suspend fun <D : Query.Data> query(query: Query<D>): ApolloResponse<D> {
return _client.query(query).execute()
}
suspend fun <D : Mutation.Data> mutation(mutation: Mutation<D>): ApolloResponse<D> {
return _client.mutation(mutation).execute()
}
suspend fun <D : Subscription.Data> subscription(subscription: Subscription<D>): ApolloResponse<D> {
return _client.subscription(subscription).execute()
}
}

View File

@@ -0,0 +1,18 @@
package app.affine.pro.service.interceptor
import android.webkit.CookieManager
import app.affine.pro.BuildConfig
import com.apollographql.apollo.api.http.HttpRequest
import com.apollographql.apollo.network.http.HttpInterceptor
import com.apollographql.apollo.network.http.HttpInterceptorChain
object CookieInterceptor : HttpInterceptor {
override suspend fun intercept(
request: HttpRequest,
chain: HttpInterceptorChain
) = chain.proceed(
request.newBuilder().addHeader(
"Cookie", CookieManager.getInstance().getCookie(BuildConfig.BASE_URL)
).build()
)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,34 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

View File

@@ -1,170 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp" android:width="108dp"
android:height="108dp" android:height="108dp"
android:viewportHeight="108" android:viewportWidth="108"
android:viewportWidth="108"> android:viewportHeight="108">
<path <path
android:fillColor="#26A69A" android:pathData="M0,0h108v108h-108z"
android:pathData="M0,0h108v108h-108z" /> android:fillColor="#ffffff"/>
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector> </vector>

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.75"
android:scaleY="0.75"
android:translateX="13.5"
android:translateY="13.5">
<path
android:pathData="M49.64,32.93C51.24,30.16 52.44,28.08 53.01,27.11L53.01,27.11C53.44,26.35 54.56,26.36 54.99,27.11C55.2,27.46 55.6,28.15 56,28.87L56,28.87C56.46,29.66 56.92,30.48 57.16,30.87C60.42,36.52 64.34,43.31 68.32,50.21L68.37,50.29C70.21,53.48 72.07,56.7 73.88,59.83L49.64,32.93ZM44.39,42.04C36.94,54.94 27.32,71.61 25.72,74.41C25.44,75.09 25.96,75.95 26.71,75.97C26.85,75.98 27.72,75.98 28.28,75.98C28.48,75.98 28.64,75.98 28.72,75.98C30.18,75.98 31.74,75.98 33.38,75.98L44.39,42.04ZM44.17,75.98C48.26,75.98 52.53,75.98 56.76,75.98C65.51,75.98 74.12,75.98 80.73,75.98L80.8,75.98H80.8C81.02,75.98 81.28,75.98 81.29,75.97C81.35,75.97 81.39,75.96 81.44,75.95L81.44,75.95C81.47,75.94 81.5,75.94 81.54,75.93C82.15,75.75 82.53,75.02 82.28,74.41C82.25,74.38 82.24,74.35 82.23,74.32C82.21,74.27 82.18,74.21 82.08,74.05C81.08,72.32 80.03,70.49 78.92,68.57L44.17,75.98ZM56.71,23.96C54.53,22.37 51.35,23.04 50.02,25.38C48.96,27.24 45.74,32.81 41.85,39.55L41.85,39.55C34.29,52.63 24.21,70.07 22.53,73.1C21.5,75.58 22.94,78.53 25.53,79.25C26.29,79.45 27.08,79.44 27.83,79.43H27.83C28.13,79.42 28.43,79.41 28.72,79.42C35.51,79.43 44.41,79.43 53.42,79.42H53.42H53.42H53.42H53.42C63.28,79.42 73.26,79.42 80.73,79.42C81.14,79.43 81.86,79.42 82.47,79.25C84.35,78.73 85.75,76.94 85.8,74.99C85.82,74.35 85.7,73.7 85.47,73.1C85.26,72.64 85.08,72.34 84.91,72.04C84.84,71.93 84.78,71.82 84.71,71.7L83.26,69.19L71.65,49.08L60.15,29.14L58.7,26.64L57.98,25.38C57.65,24.84 57.22,24.35 56.71,23.96ZM74.49,63.12C73.82,62.54 73.14,61.98 72.43,61.45L48.97,72.8C48.91,72.84 48.85,72.88 48.79,72.92L77.16,65.89L74.49,63.12ZM54.16,68.07L69.73,59.66C68.83,59.14 67.9,58.67 66.93,58.26L54.16,68.07ZM56.52,63.82L64.05,57.28C63.65,57.17 63.24,57.08 62.82,56.99C62.09,56.86 61.36,56.77 60.62,56.72L56.52,63.82ZM52.76,66.04C52.35,66.66 51.91,67.25 51.43,67.82C51.15,68.13 50.86,68.44 50.57,68.73L48.67,58.95L52.76,66.04ZM48.62,54.58C48.29,53.92 48,53.23 47.75,52.53C47.62,52.13 47.5,51.73 47.39,51.33L56.81,54.58H48.62ZM46.8,42.05L68.01,56.47C68.06,56.5 68.11,56.52 68.16,56.54L48.13,35.73L47.69,37.25C47.29,38.84 46.99,40.44 46.8,42.05ZM42.49,74.03C40.68,74.79 38.79,75.39 36.86,75.87L36.55,75.95L44.34,48.91L42.49,74.03ZM48.27,70.75C47.35,71.44 46.39,72.06 45.38,72.62L45.92,53.61C45.97,53.72 46.01,53.84 46.06,53.95L48.27,70.75ZM61.38,54.37L46.61,45.27C46.61,46.29 46.67,47.31 46.8,48.33L61.38,54.37ZM50.33,57.62L53.46,63.05H53.47C53.7,63.46 54.3,63.46 54.54,63.05L57.67,57.62C57.91,57.2 57.61,56.68 57.14,56.68H50.87C50.39,56.68 50.09,57.2 50.33,57.62Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</group>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@@ -17,6 +17,6 @@
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen"> <style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item> <item name="windowSplashScreenBackground">#FFFFFF</item>
</style> </style>
</resources> </resources>

View File

@@ -1,33 +1,42 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
repositories {
repositories { google {
google() content {
mavenCentral() includeGroupByRegex("com\\.android.*")
maven { includeGroupByRegex("com\\.google.*")
url "https://plugins.gradle.org/m2/" includeGroupByRegex("androidx.*")
}
} }
mavenCentral()
gradlePluginPortal()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.8.0' classpath libs.android.gradlePlugin
classpath 'com.google.gms:google-services:4.4.2' classpath libs.google.services
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
} }
} }
plugins { plugins {
id("org.mozilla.rust-android-gradle.rust-android") version "0.9.6" alias libs.plugins.android.application apply false
id("org.jetbrains.kotlin.jvm") version "2.1.0" alias libs.plugins.android.library apply false
alias libs.plugins.kotlin.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"
allprojects { allprojects {
repositories { repositories {
google() google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral() mavenCentral()
gradlePluginPortal()
} }
} }

View File

@@ -1,3 +1,9 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android' include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../../../../../node_modules/@capacitor/android/capacitor') project(':capacitor-android').projectDir = new File('../../../../../node_modules/@capacitor/android/capacitor')
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../../../../../node_modules/@capacitor/app/android')
include ':capgo-inappbrowser'
project(':capgo-inappbrowser').projectDir = new File('../../../../../node_modules/@capgo/inappbrowser/android')

View File

@@ -0,0 +1,41 @@
[versions]
androidxEspressoCoreVersion = "3.6.1"
androidxJunitVersion = "1.2.1"
browser = "1.8.0"
coreKtx = "1.15.0"
coreSplashScreenVersion = "1.0.1"
jna = "5.16.0"
junitVersion = "4.13.2"
kotlin = "2.1.0"
kotlinxCoroutinesCore = "1.10.1"
rustAndroid = "0.9.6"
appcompat = "1.7.0"
coordinatorLayout = "1.2.0"
googleServices = "4.4.2"
androidGradlePlugin = "8.8.0"
apollo = "4.1.0"
[libraries]
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
androidx-browser = { module = "androidx.browser:browser", version.ref = "browser" }
androidx-coordinatorlayout = { module = "androidx.coordinatorlayout:coordinatorlayout", version.ref = "coordinatorLayout" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashScreenVersion" }
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidxEspressoCoreVersion" }
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidxJunitVersion" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
google-services = { module = "com.google.gms:google-services", version.ref = "googleServices" }
android-gradlePlugin = { module = "com.android.tools.build:gradle", version.ref = "androidGradlePlugin" }
jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
junit = { module = "junit:junit", version.ref = "junitVersion" }
apollo-runtime = { module = "com.apollographql.apollo:apollo-runtime", version.ref = "apollo" }
apollo-api = { module = "com.apollographql.apollo:apollo-api", version.ref = "apollo" }
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
rust-android = { id = "org.mozilla.rust-android-gradle.rust-android", version.ref = "rustAndroid" }
apollo-android = { id = "com.apollographql.apollo", version.ref = "apollo" }
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

View File

@@ -0,0 +1 @@
/build

View File

@@ -0,0 +1,18 @@
plugins {
alias libs.plugins.jetbrains.kotlin.jvm
alias libs.plugins.apollo.android
}
dependencies {
implementation libs.apollo.api
}
apollo {
service("affine") {
packageName.set("com.affine.pro.graphql")
introspection {
endpointUrl.set("https://app.affine.pro/graphql")
schemaFile.set(file("src/main/graphql/affine/schema.graphqls"))
}
}
}

View File

@@ -0,0 +1,3 @@
mutation CreateCopilotSession($options: CreateChatSessionInput!) {
createCopilotSession(options: $options)
}

View File

@@ -1,5 +1,18 @@
include ':app' pluginManagement {
include ':capacitor-cordova-android-plugins' repositories {
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
include ':app'
include ':service'
include ':capacitor-cordova-android-plugins'
apply from: 'capacitor.settings.gradle' apply from: 'capacitor.settings.gradle'

View File

@@ -2,15 +2,4 @@ ext {
minSdkVersion = 22 minSdkVersion = 22
compileSdkVersion = 35 compileSdkVersion = 35
targetSdkVersion = 35 targetSdkVersion = 35
androidxActivityVersion = '1.8.0' }
androidxAppCompatVersion = '1.7.0'
androidxCoordinatorLayoutVersion = '1.2.0'
androidxCoreVersion = '1.12.0'
androidxFragmentVersion = '1.6.2'
coreSplashScreenVersion = '1.0.1'
androidxWebkitVersion = '1.9.0'
junitVersion = '4.13.2'
androidxJunitVersion = '1.2.1'
androidxEspressoCoreVersion = '3.6.1'
cordovaAndroidVersion = '10.1.1'
}

View File

@@ -2,6 +2,17 @@
AFFiNE Android app. AFFiNE Android app.
## Setup
- set CARGO_HOME to your system environment
- add
`rust.cargoCommand={replace_with_your_own_cargo_home_absolute_path}/bin/cargo`
`rust.rustcCommand={replace_with_your_own_cargo_home_absolute_path}/bin/rustc`
to App/local.properties
## Build ## Build
- yarn install - yarn install

View File

@@ -16,6 +16,14 @@ const config: CapacitorConfig = {
releaseType: 'AAB', releaseType: 'AAB',
}, },
}, },
plugins: {
CapacitorHttp: {
enabled: true,
},
CapacitorCookies: {
enabled: true,
},
},
}; };
export default config; export default config;

View File

@@ -15,7 +15,9 @@
"@blocksuite/affine": "workspace:*", "@blocksuite/affine": "workspace:*",
"@blocksuite/icons": "2.2.2", "@blocksuite/icons": "2.2.2",
"@capacitor/android": "^7.0.0", "@capacitor/android": "^7.0.0",
"@capacitor/app": "^7.0.0",
"@capacitor/core": "^7.0.0", "@capacitor/core": "^7.0.0",
"@capgo/inappbrowser": "^6.9.35",
"@sentry/react": "^8.44.0", "@sentry/react": "^8.44.0",
"@toeverything/infra": "workspace:*", "@toeverything/infra": "workspace:*",
"react": "^19.0.0", "react": "^19.0.0",

View File

@@ -3,15 +3,20 @@ import { AppFallback } from '@affine/core/mobile/components/app-fallback';
import { configureMobileModules } from '@affine/core/mobile/modules'; import { configureMobileModules } from '@affine/core/mobile/modules';
import { router } from '@affine/core/mobile/router'; import { router } from '@affine/core/mobile/router';
import { configureCommonModules } from '@affine/core/modules'; import { configureCommonModules } from '@affine/core/modules';
import { AuthService, DefaultServerService } from '@affine/core/modules/cloud';
import { I18nProvider } from '@affine/core/modules/i18n'; import { I18nProvider } from '@affine/core/modules/i18n';
import { LifecycleService } from '@affine/core/modules/lifecycle'; import { LifecycleService } from '@affine/core/modules/lifecycle';
import { import {
configureLocalStorageStateStorageImpls, configureLocalStorageStateStorageImpls,
NbstoreProvider, NbstoreProvider,
} from '@affine/core/modules/storage'; } from '@affine/core/modules/storage';
import { PopupWindowProvider } from '@affine/core/modules/url';
import { ClientSchemeProvider } from '@affine/core/modules/url/providers/client-schema';
import { configureBrowserWorkbenchModule } from '@affine/core/modules/workbench'; import { configureBrowserWorkbenchModule } from '@affine/core/modules/workbench';
import { configureBrowserWorkspaceFlavours } from '@affine/core/modules/workspace-engine'; import { configureBrowserWorkspaceFlavours } from '@affine/core/modules/workspace-engine';
import { WorkerClient } from '@affine/nbstore/worker/client'; import { WorkerClient } from '@affine/nbstore/worker/client';
import { App as CapacitorApp } from '@capacitor/app';
import { InAppBrowser } from '@capgo/inappbrowser';
import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra'; import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra';
import { OpClient } from '@toeverything/infra/op'; import { OpClient } from '@toeverything/infra/op';
import { Suspense } from 'react'; import { Suspense } from 'react';
@@ -43,12 +48,62 @@ framework.impl(NbstoreProvider, {
}); });
const frameworkProvider = framework.provider(); const frameworkProvider = framework.provider();
framework.impl(PopupWindowProvider, {
open: (url: string) => {
InAppBrowser.open({
url: url,
}).catch(console.error);
},
});
framework.impl(ClientSchemeProvider, {
getClientScheme() {
return 'affine';
},
});
// setup application lifecycle events, and emit application start event // setup application lifecycle events, and emit application start event
window.addEventListener('focus', () => { window.addEventListener('focus', () => {
frameworkProvider.get(LifecycleService).applicationFocus(); frameworkProvider.get(LifecycleService).applicationFocus();
}); });
frameworkProvider.get(LifecycleService).applicationStart(); frameworkProvider.get(LifecycleService).applicationStart();
CapacitorApp.addListener('appUrlOpen', ({ url }) => {
// try to close browser if it's open
InAppBrowser.close().catch(e => console.error('Failed to close browser', e));
const urlObj = new URL(url);
if (urlObj.hostname === 'authentication') {
const method = urlObj.searchParams.get('method');
const payload = JSON.parse(urlObj.searchParams.get('payload') ?? 'false');
if (
!method ||
(method !== 'magic-link' && method !== 'oauth') ||
!payload
) {
console.error('Invalid authentication url', url);
return;
}
const authService = frameworkProvider
.get(DefaultServerService)
.server.scope.get(AuthService);
if (method === 'oauth') {
authService
.signInOauth(payload.code, payload.state, payload.provider)
.catch(console.error);
} else if (method === 'magic-link') {
authService
.signInMagicLink(payload.email, payload.token)
.catch(console.error);
}
}
}).catch(e => {
console.error(e);
});
export function App() { export function App() {
return ( return (
<Suspense> <Suspense>

View File

@@ -226,8 +226,10 @@ __metadata:
"@blocksuite/affine": "workspace:*" "@blocksuite/affine": "workspace:*"
"@blocksuite/icons": "npm:2.2.2" "@blocksuite/icons": "npm:2.2.2"
"@capacitor/android": "npm:^7.0.0" "@capacitor/android": "npm:^7.0.0"
"@capacitor/app": "npm:^7.0.0"
"@capacitor/cli": "npm:^7.0.0" "@capacitor/cli": "npm:^7.0.0"
"@capacitor/core": "npm:^7.0.0" "@capacitor/core": "npm:^7.0.0"
"@capgo/inappbrowser": "npm:^6.9.35"
"@sentry/react": "npm:^8.44.0" "@sentry/react": "npm:^8.44.0"
"@toeverything/infra": "workspace:*" "@toeverything/infra": "workspace:*"
"@types/react": "npm:^19.0.1" "@types/react": "npm:^19.0.1"
@@ -4261,6 +4263,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@capgo/inappbrowser@npm:^6.9.35":
version: 6.9.35
resolution: "@capgo/inappbrowser@npm:6.9.35"
peerDependencies:
"@capacitor/core": ^6.0.0
checksum: 10/81d3ef7fd89ddb1d9862a0d1db44b75620191678e19f8c5ee99859cbb2b883c9afee3960bf0b7c4847384929e6dd3ff2c223bbdd293ceefd3fd51fc42a7010ef
languageName: node
linkType: hard
"@chromatic-com/storybook@npm:^3.2.2": "@chromatic-com/storybook@npm:^3.2.2":
version: 3.2.4 version: 3.2.4
resolution: "@chromatic-com/storybook@npm:3.2.4" resolution: "@chromatic-com/storybook@npm:3.2.4"