diff --git a/.github/workflows/release-mobile.yml b/.github/workflows/release-mobile.yml index 917234c6b1..1908ec94d4 100644 --- a/.github/workflows/release-mobile.yml +++ b/.github/workflows/release-mobile.yml @@ -231,7 +231,7 @@ jobs: - name: Build run: | 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: AFFINE_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.AFFINE_ANDROID_KEYSTORE_PASSWORD }} AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD: ${{ secrets.AFFINE_ANDROID_KEYSTORE_ALIAS_PASSWORD }} @@ -243,7 +243,7 @@ jobs: with: serviceAccountJson: ${{ steps.auth.outputs.credentials_file_path }} 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 status: draft existingEditId: ${{ steps.bump.outputs.EDIT_ID }} diff --git a/packages/frontend/apps/android/App/app/build.gradle b/packages/frontend/apps/android/App/app/build.gradle index 8b57cb84b3..5f3a4a6a7d 100644 --- a/packages/frontend/apps/android/App/app/build.gradle +++ b/packages/frontend/apps/android/App/app/build.gradle @@ -1,12 +1,16 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinVersion -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' +plugins { + alias libs.plugins.android.application + alias libs.plugins.kotlin.android + alias libs.plugins.rust.android +} + apply from: 'capacitor.build.gradle' android { - namespace "app.affine.pro" + namespace "app.affine.pro" compileSdk rootProject.ext.compileSdkVersion ndkVersion = new File(sdkDirectory, "ndk").listFiles().sort().last().name defaultConfig { @@ -17,20 +21,35 @@ android { versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { - // 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 + // 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 ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' } ndk { abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64' } } + buildFeatures { + buildConfig true + } buildTypes { release { minifyEnabled false 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 { sourceCompatibility JavaVersion.VERSION_21 targetCompatibility JavaVersion.VERSION_21 @@ -42,20 +61,22 @@ repositories { dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' } } - dependencies { 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') - testImplementation "junit:junit:$junitVersion" - androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" - androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" implementation project(':capacitor-cordova-android-plugins') - implementation "net.java.dev.jna:jna:5.16.0@aar" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1" - implementation 'androidx.core:core-ktx:1.15.0' + implementation project(':service') + implementation libs.kotlinx.coroutines.core + 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 { @@ -63,28 +84,26 @@ try { if (servicesJSON.text) { 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") } -apply plugin: 'org.mozilla.rust-android-gradle.rust-android' - cargo { - module = "../../../../mobile-native" - libname = "affine_mobile_native" - targets = ["arm64"] - pythonCommand = "python3.12" - targetDirectory = "../../../../../../target" - apiLevel = 28 - targetIncludes = ["libaffine_mobile_native.so"] - profile = "release" + module = "../../../../mobile-native" + libname = "affine_mobile_native" + targets = ["arm64"] + pythonCommand = "python3.12" + targetDirectory = "../../../../../../target" + apiLevel = 28 + targetIncludes = ["libaffine_mobile_native.so"] + profile = "release" } kotlin { - compilerOptions { - apiVersion = KotlinVersion.KOTLIN_2_0 - jvmTarget = JvmTarget.JVM_21 - } + compilerOptions { + apiVersion = KotlinVersion.KOTLIN_2_1 + jvmTarget = JvmTarget.JVM_21 + } } afterEvaluate { @@ -103,7 +122,7 @@ android.applicationVariants.configureEach { variant -> def t = tasks.register("generate${variant.name.capitalize()}UniFFIBindings", Exec) { workingDir "${project.projectDir}" // 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") } variant.javaCompileProvider.get().dependsOn(t) diff --git a/packages/frontend/apps/android/App/app/capacitor.build.gradle b/packages/frontend/apps/android/App/app/capacitor.build.gradle index fdb4970c41..aebef209b3 100644 --- a/packages/frontend/apps/android/App/app/capacitor.build.gradle +++ b/packages/frontend/apps/android/App/app/capacitor.build.gradle @@ -2,14 +2,15 @@ android { compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } } apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { - + implementation project(':capacitor-app') + implementation project(':capgo-inappbrowser') } diff --git a/packages/frontend/apps/android/App/app/src/main/AndroidManifest.xml b/packages/frontend/apps/android/App/app/src/main/AndroidManifest.xml index 4d7ca38041..d8249c6476 100644 --- a/packages/frontend/apps/android/App/app/src/main/AndroidManifest.xml +++ b/packages/frontend/apps/android/App/app/src/main/AndroidManifest.xml @@ -1,6 +1,12 @@ + + + + + + + + + + + + query(query: Query): ApolloResponse { + return _client.query(query).execute() + } + + suspend fun mutation(mutation: Mutation): ApolloResponse { + return _client.mutation(mutation).execute() + } + + suspend fun subscription(subscription: Subscription): ApolloResponse { + return _client.subscription(subscription).execute() + } +} \ No newline at end of file diff --git a/packages/frontend/apps/android/App/app/src/main/java/app/affine/pro/service/interceptor/CookieInterceptor.kt b/packages/frontend/apps/android/App/app/src/main/java/app/affine/pro/service/interceptor/CookieInterceptor.kt new file mode 100644 index 0000000000..8f557a63e0 --- /dev/null +++ b/packages/frontend/apps/android/App/app/src/main/java/app/affine/pro/service/interceptor/CookieInterceptor.kt @@ -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() + ) +} \ No newline at end of file diff --git a/packages/frontend/apps/android/App/app/src/main/res/drawable-land-hdpi/splash.png b/packages/frontend/apps/android/App/app/src/main/res/drawable-land-hdpi/splash.png deleted file mode 100644 index e31573b4fc..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/drawable-land-hdpi/splash.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/drawable-land-mdpi/splash.png b/packages/frontend/apps/android/App/app/src/main/res/drawable-land-mdpi/splash.png deleted file mode 100644 index f7a64923ea..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/drawable-land-mdpi/splash.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/drawable-land-xhdpi/splash.png b/packages/frontend/apps/android/App/app/src/main/res/drawable-land-xhdpi/splash.png deleted file mode 100644 index 807725501b..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/drawable-land-xhdpi/splash.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/drawable-land-xxhdpi/splash.png b/packages/frontend/apps/android/App/app/src/main/res/drawable-land-xxhdpi/splash.png deleted file mode 100644 index 14c6c8fe39..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/drawable-land-xxhdpi/splash.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/drawable-land-xxxhdpi/splash.png b/packages/frontend/apps/android/App/app/src/main/res/drawable-land-xxxhdpi/splash.png deleted file mode 100644 index 244ca2506d..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/drawable-land-xxxhdpi/splash.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/drawable-port-hdpi/splash.png b/packages/frontend/apps/android/App/app/src/main/res/drawable-port-hdpi/splash.png deleted file mode 100644 index 74faaa583c..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/drawable-port-hdpi/splash.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/drawable-port-mdpi/splash.png b/packages/frontend/apps/android/App/app/src/main/res/drawable-port-mdpi/splash.png deleted file mode 100644 index e944f4ad4e..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/drawable-port-mdpi/splash.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/drawable-port-xhdpi/splash.png b/packages/frontend/apps/android/App/app/src/main/res/drawable-port-xhdpi/splash.png deleted file mode 100644 index 564a82ff95..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/drawable-port-xhdpi/splash.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/drawable-port-xxhdpi/splash.png b/packages/frontend/apps/android/App/app/src/main/res/drawable-port-xxhdpi/splash.png deleted file mode 100644 index bfabe6871a..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/drawable-port-xxhdpi/splash.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/drawable-port-xxxhdpi/splash.png b/packages/frontend/apps/android/App/app/src/main/res/drawable-port-xxxhdpi/splash.png deleted file mode 100644 index 6929071268..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/drawable-port-xxxhdpi/splash.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/packages/frontend/apps/android/App/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index c7bd21dbd8..0000000000 --- a/packages/frontend/apps/android/App/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/packages/frontend/apps/android/App/app/src/main/res/drawable/ic_launcher_background.xml b/packages/frontend/apps/android/App/app/src/main/res/drawable/ic_launcher_background.xml index d5fccc538c..90460888e2 100644 --- a/packages/frontend/apps/android/App/app/src/main/res/drawable/ic_launcher_background.xml +++ b/packages/frontend/apps/android/App/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,170 +1,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:viewportWidth="108" + android:viewportHeight="108"> + diff --git a/packages/frontend/apps/android/App/app/src/main/res/drawable/ic_launcher_foreground.xml b/packages/frontend/apps/android/App/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000000..9661c4f52d --- /dev/null +++ b/packages/frontend/apps/android/App/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/packages/frontend/apps/android/App/app/src/main/res/drawable/splash.png b/packages/frontend/apps/android/App/app/src/main/res/drawable/splash.png deleted file mode 100644 index f7a64923ea..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/drawable/splash.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/frontend/apps/android/App/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 036d09bc5f..bbd3e02123 100644 --- a/packages/frontend/apps/android/App/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/packages/frontend/apps/android/App/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/packages/frontend/apps/android/App/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 036d09bc5f..bbd3e02123 100644 --- a/packages/frontend/apps/android/App/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/packages/frontend/apps/android/App/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c023e50595..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000..a9304aee6c Binary files /dev/null and b/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 2127973b2d..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index b441f37d6a..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000..2dccc028ce Binary files /dev/null and b/packages/frontend/apps/android/App/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 72905b854c..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000..e2b9179412 Binary files /dev/null and b/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index 8ed0605c27..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 9502e47a2c..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000000..2ccff768d9 Binary files /dev/null and b/packages/frontend/apps/android/App/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 4d1e077104..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000..841ff065d9 Binary files /dev/null and b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index df0f15880b..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 853db043d8..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000..8a2b6c4a2b Binary files /dev/null and b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6cdf97c119..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000..b84f0659a7 Binary files /dev/null and b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 2960cbb610..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 8e3093a86f..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000..3784e04a2b Binary files /dev/null and b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 46de6e255a..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000000..79b8213ea7 Binary files /dev/null and b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index d2ea9abed3..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index a40d73e9c6..0000000000 Binary files a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000..d5d79869b5 Binary files /dev/null and b/packages/frontend/apps/android/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/packages/frontend/apps/android/App/app/src/main/res/values/ic_launcher_background.xml b/packages/frontend/apps/android/App/app/src/main/res/values/ic_launcher_background.xml deleted file mode 100644 index c5d5899fdf..0000000000 --- a/packages/frontend/apps/android/App/app/src/main/res/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #FFFFFF - \ No newline at end of file diff --git a/packages/frontend/apps/android/App/app/src/main/res/values/styles.xml b/packages/frontend/apps/android/App/app/src/main/res/values/styles.xml index be874e54a4..96e9352098 100644 --- a/packages/frontend/apps/android/App/app/src/main/res/values/styles.xml +++ b/packages/frontend/apps/android/App/app/src/main/res/values/styles.xml @@ -17,6 +17,6 @@ \ No newline at end of file diff --git a/packages/frontend/apps/android/App/build.gradle b/packages/frontend/apps/android/App/build.gradle index 524bc0a59e..0a09f92da6 100644 --- a/packages/frontend/apps/android/App/build.gradle +++ b/packages/frontend/apps/android/App/build.gradle @@ -1,33 +1,42 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { - - repositories { - google() - mavenCentral() - maven { - url "https://plugins.gradle.org/m2/" + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } } + mavenCentral() + gradlePluginPortal() } dependencies { - classpath 'com.android.tools.build:gradle:8.8.0' - classpath 'com.google.gms:google-services:4.4.2' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath libs.android.gradlePlugin + classpath libs.google.services } } plugins { - id("org.mozilla.rust-android-gradle.rust-android") version "0.9.6" - id("org.jetbrains.kotlin.jvm") version "2.1.0" + alias libs.plugins.android.application apply false + 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" allprojects { repositories { - google() + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } mavenCentral() + gradlePluginPortal() } -} +} \ No newline at end of file diff --git a/packages/frontend/apps/android/App/capacitor.settings.gradle b/packages/frontend/apps/android/App/capacitor.settings.gradle index 60dade133e..ac95c8a879 100644 --- a/packages/frontend/apps/android/App/capacitor.settings.gradle +++ b/packages/frontend/apps/android/App/capacitor.settings.gradle @@ -1,3 +1,9 @@ // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN include ':capacitor-android' 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') diff --git a/packages/frontend/apps/android/App/gradle/libs.versions.toml b/packages/frontend/apps/android/App/gradle/libs.versions.toml new file mode 100644 index 0000000000..8d48c250c7 --- /dev/null +++ b/packages/frontend/apps/android/App/gradle/libs.versions.toml @@ -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" } diff --git a/packages/frontend/apps/android/App/service/.gitignore b/packages/frontend/apps/android/App/service/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/packages/frontend/apps/android/App/service/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/packages/frontend/apps/android/App/service/build.gradle b/packages/frontend/apps/android/App/service/build.gradle new file mode 100644 index 0000000000..337d0ebaee --- /dev/null +++ b/packages/frontend/apps/android/App/service/build.gradle @@ -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")) + } + } +} \ No newline at end of file diff --git a/packages/frontend/apps/android/App/service/src/main/graphql/affine/CreateCopilotSession.graphql b/packages/frontend/apps/android/App/service/src/main/graphql/affine/CreateCopilotSession.graphql new file mode 100644 index 0000000000..0a9b74750c --- /dev/null +++ b/packages/frontend/apps/android/App/service/src/main/graphql/affine/CreateCopilotSession.graphql @@ -0,0 +1,3 @@ +mutation CreateCopilotSession($options: CreateChatSessionInput!) { + createCopilotSession(options: $options) +} diff --git a/packages/frontend/apps/android/App/service/src/main/graphql/affine/schema.graphqls b/packages/frontend/apps/android/App/service/src/main/graphql/affine/schema.graphqls new file mode 100644 index 0000000000..c37c48e88b --- /dev/null +++ b/packages/frontend/apps/android/App/service/src/main/graphql/affine/schema.graphqls @@ -0,0 +1,2027 @@ +type AlreadyInSpaceDataType { + spaceId: String! +} + +type BlobNotFoundDataType { + blobId: String! + + spaceId: String! +} + +enum ChatHistoryOrder { + asc + + desc +} + +type ChatMessage { + attachments: [String!] + + content: String! + + createdAt: DateTime! + + id: ID + + params: JSON + + role: String! +} + +type Copilot { + """ + Get the session list of actions in the workspace + """ + actions: [String!]! + + """ + Get the session list of chats in the workspace + """ + chats: [String!]! + + histories( + docId: String + options: QueryChatHistoriesInput + ): [CopilotHistories!]! + + """ + Get the quota of the user in the workspace + """ + quota: CopilotQuota! + + workspaceId: ID +} + +type CopilotHistories { + """ + An mark identifying which view to use to display the session + """ + action: String + + createdAt: DateTime! + + messages: [ChatMessage!]! + + sessionId: String! + + """ + The number of tokens used in the session + """ + tokens: Int! +} + +type CopilotMessageNotFoundDataType { + messageId: String! +} + +enum CopilotModels { + DallE3 + + Gpt4Omni + + Gpt4Omni0806 + + Gpt4OmniMini + + Gpt4OmniMini0718 + + TextEmbedding3Large + + TextEmbedding3Small + + TextEmbeddingAda002 + + TextModerationLatest + + TextModerationStable +} + +input CopilotPromptConfigInput { + frequencyPenalty: Float + + jsonMode: Boolean + + presencePenalty: Float + + temperature: Float + + topP: Float +} + +type CopilotPromptConfigType { + frequencyPenalty: Float + + jsonMode: Boolean + + presencePenalty: Float + + temperature: Float + + topP: Float +} + +input CopilotPromptMessageInput { + content: String! + + params: JSON + + role: CopilotPromptMessageRole! +} + +enum CopilotPromptMessageRole { + assistant + + system + + user +} + +type CopilotPromptMessageType { + content: String! + + params: JSON + + role: CopilotPromptMessageRole! +} + +type CopilotPromptNotFoundDataType { + name: String! +} + +type CopilotPromptType { + action: String + + config: CopilotPromptConfigType + + messages: [CopilotPromptMessageType!]! + + model: String! + + name: String! +} + +type CopilotProviderSideErrorDataType { + kind: String! + + message: String! + + provider: String! +} + +type CopilotQuota { + limit: SafeInt + + used: SafeInt! +} + +input CreateChatMessageInput { + attachments: [String!] + + blobs: [Upload!] + + content: String + + params: JSON + + sessionId: String! +} + +input CreateChatSessionInput { + docId: String! + + """ + The prompt name to use for the session + """ + promptName: String! + + workspaceId: String! +} + +input CreateCheckoutSessionInput { + args: JSONObject + + coupon: String + + idempotencyKey: String + + plan: SubscriptionPlan = Pro + + recurring: SubscriptionRecurring = Yearly + + successCallbackLink: String! + + variant: SubscriptionVariant +} + +input CreateCopilotPromptInput { + action: String + + config: CopilotPromptConfigInput + + messages: [CopilotPromptMessageInput!]! + + model: CopilotModels! + + name: String! +} + +input CreateUserInput { + email: String! + + name: String +} + +type CredentialsRequirementType { + password: PasswordLimitsType! +} + +""" +A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date-time format. +""" +scalar DateTime + +type DeleteAccount { + success: Boolean! +} + +input DeleteSessionInput { + docId: String! + + sessionIds: [String!]! + + workspaceId: String! +} + +type DocAccessDeniedDataType { + docId: String! + + spaceId: String! +} + +type DocHistoryNotFoundDataType { + docId: String! + + spaceId: String! + + timestamp: Int! +} + +type DocHistoryType { + editor: EditorType + + id: String! + + timestamp: DateTime! + + workspaceId: String! +} + +type DocNotFoundDataType { + docId: String! + + spaceId: String! +} + +type EditorType { + avatarUrl: String + + name: String! +} + +union ErrorDataUnion = + | AlreadyInSpaceDataType + | BlobNotFoundDataType + | CopilotMessageNotFoundDataType + | CopilotPromptNotFoundDataType + | CopilotProviderSideErrorDataType + | DocAccessDeniedDataType + | DocHistoryNotFoundDataType + | DocNotFoundDataType + | InvalidEmailDataType + | InvalidHistoryTimestampDataType + | InvalidPasswordLengthDataType + | InvalidRuntimeConfigTypeDataType + | MemberNotFoundInSpaceDataType + | MissingOauthQueryParameterDataType + | NotInSpaceDataType + | RuntimeConfigNotFoundDataType + | SameSubscriptionRecurringDataType + | SpaceAccessDeniedDataType + | SpaceNotFoundDataType + | SpaceOwnerNotFoundDataType + | SubscriptionAlreadyExistsDataType + | SubscriptionNotExistsDataType + | SubscriptionPlanNotFoundDataType + | UnknownOauthProviderDataType + | UnsupportedSubscriptionPlanDataType + | VersionRejectedDataType + | WrongSignInCredentialsDataType + +enum ErrorNames { + ACCESS_DENIED + + ACTION_FORBIDDEN + + ALREADY_IN_SPACE + + AUTHENTICATION_REQUIRED + + BLOB_NOT_FOUND + + BLOB_QUOTA_EXCEEDED + + CANNOT_DELETE_ALL_ADMIN_ACCOUNT + + CANNOT_DELETE_OWN_ACCOUNT + + CANT_UPDATE_ONETIME_PAYMENT_SUBSCRIPTION + + CAPTCHA_VERIFICATION_FAILED + + COPILOT_ACTION_TAKEN + + COPILOT_FAILED_TO_CREATE_MESSAGE + + COPILOT_FAILED_TO_GENERATE_TEXT + + COPILOT_MESSAGE_NOT_FOUND + + COPILOT_PROMPT_INVALID + + COPILOT_PROMPT_NOT_FOUND + + COPILOT_PROVIDER_SIDE_ERROR + + COPILOT_QUOTA_EXCEEDED + + COPILOT_SESSION_DELETED + + COPILOT_SESSION_NOT_FOUND + + CUSTOMER_PORTAL_CREATE_FAILED + + DOC_ACCESS_DENIED + + DOC_HISTORY_NOT_FOUND + + DOC_NOT_FOUND + + EARLY_ACCESS_REQUIRED + + EMAIL_ALREADY_USED + + EMAIL_TOKEN_NOT_FOUND + + EMAIL_VERIFICATION_REQUIRED + + EXPECT_TO_PUBLISH_PAGE + + EXPECT_TO_REVOKE_PUBLIC_PAGE + + FAILED_TO_CHECKOUT + + FAILED_TO_SAVE_UPDATES + + FAILED_TO_UPSERT_SNAPSHOT + + INTERNAL_SERVER_ERROR + + INVALID_CHECKOUT_PARAMETERS + + INVALID_EMAIL + + INVALID_EMAIL_TOKEN + + INVALID_HISTORY_TIMESTAMP + + INVALID_OAUTH_CALLBACK_STATE + + INVALID_PASSWORD_LENGTH + + INVALID_RUNTIME_CONFIG_TYPE + + INVALID_SUBSCRIPTION_PARAMETERS + + LINK_EXPIRED + + MAILER_SERVICE_IS_NOT_CONFIGURED + + MEMBER_NOT_FOUND_IN_SPACE + + MEMBER_QUOTA_EXCEEDED + + MISSING_OAUTH_QUERY_PARAMETER + + NOT_FOUND + + NOT_IN_SPACE + + NO_COPILOT_PROVIDER_AVAILABLE + + OAUTH_ACCOUNT_ALREADY_CONNECTED + + OAUTH_STATE_EXPIRED + + PAGE_IS_NOT_PUBLIC + + PASSWORD_REQUIRED + + RUNTIME_CONFIG_NOT_FOUND + + SAME_EMAIL_PROVIDED + + SAME_SUBSCRIPTION_RECURRING + + SIGN_UP_FORBIDDEN + + SPACE_ACCESS_DENIED + + SPACE_NOT_FOUND + + SPACE_OWNER_NOT_FOUND + + SUBSCRIPTION_ALREADY_EXISTS + + SUBSCRIPTION_EXPIRED + + SUBSCRIPTION_HAS_BEEN_CANCELED + + SUBSCRIPTION_HAS_NOT_BEEN_CANCELED + + SUBSCRIPTION_NOT_EXISTS + + SUBSCRIPTION_PLAN_NOT_FOUND + + TOO_MANY_REQUEST + + UNKNOWN_OAUTH_PROVIDER + + UNSPLASH_IS_NOT_CONFIGURED + + UNSUPPORTED_SUBSCRIPTION_PLAN + + USER_AVATAR_NOT_FOUND + + USER_NOT_FOUND + + VERSION_REJECTED + + WORKSPACE_ID_REQUIRED_FOR_TEAM_SUBSCRIPTION + + WORKSPACE_ID_REQUIRED_TO_UPDATE_TEAM_SUBSCRIPTION + + WRONG_SIGN_IN_CREDENTIALS + + WRONG_SIGN_IN_METHOD +} + +""" +The type of workspace feature +""" +enum FeatureType { + AIEarlyAccess + + Admin + + Copilot + + EarlyAccess + + UnlimitedCopilot + + UnlimitedWorkspace +} + +input ForkChatSessionInput { + docId: String! + + """ + Identify a message in the array and keep it with all previous messages into a forked session. + """ + latestMessageId: String! + + sessionId: String! + + workspaceId: String! +} + +type HumanReadableQuotaType { + blobLimit: String! + + copilotActionLimit: String + + historyPeriod: String! + + memberLimit: String! + + name: String! + + storageQuota: String! +} + +type InvalidEmailDataType { + email: String! +} + +type InvalidHistoryTimestampDataType { + timestamp: String! +} + +type InvalidPasswordLengthDataType { + max: Int! + + min: Int! +} + +type InvalidRuntimeConfigTypeDataType { + get: String! + + key: String! + + want: String! +} + +type InvitationType { + """ + Invitee information + """ + invitee: UserType! + + """ + User information + """ + user: UserType! + + """ + Workspace information + """ + workspace: InvitationWorkspaceType! +} + +type InvitationWorkspaceType { + """ + Base64 encoded avatar + """ + avatar: String! + + id: ID! + + """ + Workspace name + """ + name: String! +} + +type InviteLink { + """ + Invite link expire time + """ + expireTime: DateTime! + + """ + Invite link + """ + link: String! +} + +type InviteResult { + email: String! + + """ + Invite id, null if invite record create failed + """ + inviteId: String + + """ + Invite email sent success + """ + sentSuccess: Boolean! +} + +type InviteUserType { + """ + User accepted + """ + accepted: Boolean! @deprecated(reason: "Use `status` instead") + + """ + User avatar url + """ + avatarUrl: String + + """ + User email verified + """ + createdAt: DateTime @deprecated(reason: "useless") + + """ + User email + """ + email: String + + """ + User email verified + """ + emailVerified: Boolean + + """ + User password has been set + """ + hasPassword: Boolean + + id: ID! + + """ + Invite id + """ + inviteId: String! + + """ + User name + """ + name: String + + """ + User permission in workspace + """ + permission: Permission! + + """ + Member invite status in workspace + """ + status: WorkspaceMemberStatus! +} + +enum InvoiceStatus { + Draft + + Open + + Paid + + Uncollectible + + Void +} + +type InvoiceType { + amount: Int! + + createdAt: DateTime! + + currency: String! + + id: String @deprecated(reason: "removed") + + lastPaymentError: String + + link: String + + plan: SubscriptionPlan @deprecated(reason: "removed") + + reason: String! + + recurring: SubscriptionRecurring @deprecated(reason: "removed") + + status: InvoiceStatus! + + updatedAt: DateTime! +} + +""" +The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). +""" +scalar JSON + +""" +The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). +""" +scalar JSONObject + +type LimitedUserType { + """ + User email + """ + email: String! + + """ + User password has been set + """ + hasPassword: Boolean +} + +input ListUserInput { + first: Int = 20 + + skip: Int = 0 +} + +type ListedBlob { + createdAt: String! + + key: String! + + mime: String! + + size: Int! +} + +input ManageUserInput { + """ + User email + """ + email: String + + """ + User name + """ + name: String +} + +type MemberNotFoundInSpaceDataType { + spaceId: String! +} + +type MissingOauthQueryParameterDataType { + name: String! +} + +type Mutation { + acceptInviteById( + inviteId: String! + sendAcceptMail: Boolean + workspaceId: String! + ): Boolean! + + addWorkspaceFeature(feature: FeatureType!, workspaceId: String!): Int! + + approveMember(userId: String!, workspaceId: String!): String! + + cancelSubscription( + idempotencyKey: String @deprecated(reason: "use header `Idempotency-Key`") + plan: SubscriptionPlan = Pro + workspaceId: String + ): SubscriptionType! + + changeEmail(email: String!, token: String!): UserType! + + changePassword(newPassword: String!, token: String!, userId: String): Boolean! + + """ + Cleanup sessions + """ + cleanupCopilotSession(options: DeleteSessionInput!): [String!]! + + """ + Create change password url + """ + createChangePasswordUrl(callbackUrl: String!, userId: String!): String! + + """ + Create a subscription checkout link of stripe + """ + createCheckoutSession(input: CreateCheckoutSessionInput!): String! + + """ + Create a chat message + """ + createCopilotMessage(options: CreateChatMessageInput!): String! + + """ + Create a copilot prompt + """ + createCopilotPrompt(input: CreateCopilotPromptInput!): CopilotPromptType! + + """ + Create a chat session + """ + createCopilotSession(options: CreateChatSessionInput!): String! + + """ + Create a stripe customer portal to manage payment methods + """ + createCustomerPortal: String! + + createInviteLink( + expireTime: WorkspaceInviteLinkExpireTime! + workspaceId: String! + ): InviteLink! + + """ + Create a new user + """ + createUser(input: CreateUserInput!): UserType! + + """ + Create a new workspace + """ + createWorkspace(init: Upload): WorkspaceType! + + deleteAccount: DeleteAccount! + + deleteBlob( + hash: String @deprecated(reason: "use parameter [key]") + key: String + permanently: Boolean! = false + workspaceId: String! + ): Boolean! + + """ + Delete a user account + """ + deleteUser(id: String!): DeleteAccount! + + deleteWorkspace(id: String!): Boolean! + + """ + Create a chat session + """ + forkCopilotSession(options: ForkChatSessionInput!): String! + + grantMember( + permission: Permission! + userId: String! + workspaceId: String! + ): String! + + invite( + email: String! + permission: Permission @deprecated(reason: "never used") + sendInviteMail: Boolean + workspaceId: String! + ): String! + + inviteBatch( + emails: [String!]! + sendInviteMail: Boolean + workspaceId: String! + ): [InviteResult!]! + + leaveWorkspace( + sendLeaveMail: Boolean + workspaceId: String! + workspaceName: String @deprecated(reason: "no longer used") + ): Boolean! + + publishPage( + mode: PublicPageMode = Page + pageId: String! + workspaceId: String! + ): WorkspacePage! + + recoverDoc( + guid: String! + timestamp: DateTime! + workspaceId: String! + ): DateTime! + + releaseDeletedBlobs(workspaceId: String!): Boolean! + + """ + Remove user avatar + """ + removeAvatar: RemoveAvatar! + + removeWorkspaceFeature(feature: FeatureType!, workspaceId: String!): Int! + + resumeSubscription( + idempotencyKey: String @deprecated(reason: "use header `Idempotency-Key`") + plan: SubscriptionPlan = Pro + workspaceId: String + ): SubscriptionType! + + revoke(userId: String!, workspaceId: String!): Boolean! + + revokeInviteLink(workspaceId: String!): Boolean! + + revokePage(pageId: String!, workspaceId: String!): Boolean! + @deprecated(reason: "use revokePublicPage") + + revokePublicPage(pageId: String!, workspaceId: String!): WorkspacePage! + + sendChangeEmail(callbackUrl: String!, email: String): Boolean! + + sendChangePasswordEmail( + callbackUrl: String! + email: String @deprecated(reason: "fetched from signed in user") + ): Boolean! + + sendSetPasswordEmail( + callbackUrl: String! + email: String @deprecated(reason: "fetched from signed in user") + ): Boolean! + + sendVerifyChangeEmail( + callbackUrl: String! + email: String! + token: String! + ): Boolean! + + sendVerifyEmail(callbackUrl: String!): Boolean! + + setBlob(blob: Upload!, workspaceId: String!): String! + + setWorkspaceExperimentalFeature( + enable: Boolean! + feature: FeatureType! + workspaceId: String! + ): Boolean! + + sharePage(pageId: String!, workspaceId: String!): Boolean! + @deprecated(reason: "renamed to publishPage") + + """ + Update a copilot prompt + """ + updateCopilotPrompt( + messages: [CopilotPromptMessageInput!]! + name: String! + ): CopilotPromptType! + + updateProfile(input: UpdateUserInput!): UserType! + + """ + update server runtime configurable setting + """ + updateRuntimeConfig(id: String!, value: JSON!): ServerRuntimeConfigType! + + """ + update multiple server runtime configurable settings + """ + updateRuntimeConfigs(updates: JSONObject!): [ServerRuntimeConfigType!]! + + updateSubscriptionRecurring( + idempotencyKey: String @deprecated(reason: "use header `Idempotency-Key`") + plan: SubscriptionPlan = Pro + recurring: SubscriptionRecurring! + workspaceId: String + ): SubscriptionType! + + """ + Update a user + """ + updateUser(id: String!, input: ManageUserInput!): UserType! + + """ + update user enabled feature + """ + updateUserFeatures(features: [FeatureType!]!, id: String!): [FeatureType!]! + + """ + Update workspace + """ + updateWorkspace(input: UpdateWorkspaceInput!): WorkspaceType! + + """ + Upload user avatar + """ + uploadAvatar(avatar: Upload!): UserType! + + verifyEmail(token: String!): Boolean! +} + +type NotInSpaceDataType { + spaceId: String! +} + +enum OAuthProviderType { + GitHub + + Google + + OIDC +} + +type PasswordLimitsType { + maxLength: Int! + + minLength: Int! +} + +""" +User permission in workspace +""" +enum Permission { + Admin + + Owner + + Read + + Write +} + +""" +The mode which the public page default in +""" +enum PublicPageMode { + Edgeless + + Page +} + +type Query { + collectAllBlobSizes: WorkspaceBlobSizes! + @deprecated(reason: "use `user.quotaUsage` instead") + + """ + Get current user + """ + currentUser: UserType + + error(name: ErrorNames!): ErrorDataUnion! + + """ + send workspace invitation + """ + getInviteInfo(inviteId: String!): InvitationType! + + """ + Get is admin of workspace + """ + isAdmin(workspaceId: String!): Boolean! + + """ + Get is owner of workspace + """ + isOwner(workspaceId: String!): Boolean! + + """ + List blobs of workspace + """ + listBlobs(workspaceId: String!): [String!]! + @deprecated(reason: "use `workspace.blobs` instead") + + """ + List all copilot prompts + """ + listCopilotPrompts: [CopilotPromptType!]! + + listWorkspaceFeatures(feature: FeatureType!): [WorkspaceType!]! + + prices: [SubscriptionPrice!]! + + """ + server config + """ + serverConfig: ServerConfigType! + + """ + get all server runtime configurable settings + """ + serverRuntimeConfig: [ServerRuntimeConfigType!]! + + serverServiceConfigs: [ServerServiceConfig!]! + + """ + Get user by email + """ + user(email: String!): UserOrLimitedUser + + """ + Get user by email for admin + """ + userByEmail(email: String!): UserType + + """ + Get user by id + """ + userById(id: String!): UserType! + + """ + List registered users + """ + users(filter: ListUserInput!): [UserType!]! + + """ + Get users count + """ + usersCount: Int! + + """ + Get workspace by id + """ + workspace(id: String!): WorkspaceType! + + """ + Get all accessible workspaces for current user + """ + workspaces: [WorkspaceType!]! +} + +input QueryChatHistoriesInput { + action: Boolean + + fork: Boolean + + limit: Int + + messageOrder: ChatHistoryOrder + + sessionId: String + + sessionOrder: ChatHistoryOrder + + skip: Int +} + +type QuotaQueryType { + blobLimit: SafeInt! + + copilotActionLimit: SafeInt + + historyPeriod: SafeInt! + + humanReadable: HumanReadableQuotaType! + + memberCount: SafeInt! + + memberLimit: SafeInt! + + name: String! + + storageQuota: SafeInt! + + usedSize: SafeInt! +} + +type RemoveAvatar { + success: Boolean! +} + +type RuntimeConfigNotFoundDataType { + key: String! +} + +enum RuntimeConfigType { + Array + + Boolean + + Number + + Object + + String +} + +""" +The `SafeInt` scalar type represents non-fractional signed whole numeric values that are considered safe as defined by the ECMAScript specification. +""" +scalar SafeInt + +type SameSubscriptionRecurringDataType { + recurring: String! +} + +type ServerConfigType { + """ + Features for user that can be configured + """ + availableUserFeatures: [FeatureType!]! + + """ + server base url + """ + baseUrl: String! + + """ + credentials requirement + """ + credentialsRequirement: CredentialsRequirementType! + + """ + enable telemetry + """ + enableTelemetry: Boolean! + + """ + enabled server features + """ + features: [ServerFeature!]! + + """ + server flags + """ + flags: ServerFlagsType! + + """ + server flavor + """ + flavor: String! @deprecated(reason: "use `features`") + + """ + whether server has been initialized + """ + initialized: Boolean! + + """ + server identical name could be shown as badge on user interface + """ + name: String! + + oauthProviders: [OAuthProviderType!]! + + """ + server type + """ + type: ServerDeploymentType! + + """ + server version + """ + version: String! +} + +enum ServerDeploymentType { + Affine + + Selfhosted +} + +enum ServerFeature { + Captcha + + Copilot + + OAuth + + Payment +} + +type ServerFlagsType { + earlyAccessControl: Boolean! + + syncClientVersionCheck: Boolean! +} + +type ServerRuntimeConfigType { + description: String! + + id: String! + + key: String! + + module: String! + + type: RuntimeConfigType! + + updatedAt: DateTime! + + value: JSON! +} + +type ServerServiceConfig { + config: JSONObject! + + name: String! +} + +type SpaceAccessDeniedDataType { + spaceId: String! +} + +type SpaceNotFoundDataType { + spaceId: String! +} + +type SpaceOwnerNotFoundDataType { + spaceId: String! +} + +type SubscriptionAlreadyExistsDataType { + plan: String! +} + +type SubscriptionNotExistsDataType { + plan: String! +} + +enum SubscriptionPlan { + AI + + Enterprise + + Free + + Pro + + SelfHosted + + Team +} + +type SubscriptionPlanNotFoundDataType { + plan: String! + + recurring: String! +} + +type SubscriptionPrice { + amount: Int + + currency: String! + + lifetimeAmount: Int + + plan: SubscriptionPlan! + + type: String! + + yearlyAmount: Int +} + +enum SubscriptionRecurring { + Lifetime + + Monthly + + Yearly +} + +enum SubscriptionStatus { + Active + + Canceled + + Incomplete + + IncompleteExpired + + PastDue + + Paused + + Trialing + + Unpaid +} + +type SubscriptionType { + canceledAt: DateTime + + createdAt: DateTime! + + end: DateTime + + id: String @deprecated(reason: "removed") + + nextBillAt: DateTime + + """ + The 'Free' plan just exists to be a placeholder and for the type convenience of frontend. + There won't actually be a subscription with plan 'Free' + """ + plan: SubscriptionPlan! + + recurring: SubscriptionRecurring! + + start: DateTime! + + status: SubscriptionStatus! + + trialEnd: DateTime + + trialStart: DateTime + + updatedAt: DateTime! + + variant: SubscriptionVariant +} + +enum SubscriptionVariant { + EA + + Onetime +} + +type UnknownOauthProviderDataType { + name: String! +} + +type UnsupportedSubscriptionPlanDataType { + plan: String! +} + +input UpdateUserInput { + """ + User name + """ + name: String +} + +input UpdateWorkspaceInput { + """ + Enable AI + """ + enableAi: Boolean + + """ + Enable url previous when sharing + """ + enableUrlPreview: Boolean + + id: ID! + + """ + is Public workspace + """ + public: Boolean +} + +""" +The `Upload` scalar type represents a file upload. +""" +scalar Upload + +union UserOrLimitedUser = LimitedUserType | UserType + +type UserQuota { + blobLimit: SafeInt! + + historyPeriod: SafeInt! + + humanReadable: UserQuotaHumanReadable! + + memberLimit: Int! + + name: String! + + storageQuota: SafeInt! +} + +type UserQuotaHumanReadable { + blobLimit: String! + + historyPeriod: String! + + memberLimit: String! + + name: String! + + storageQuota: String! +} + +type UserQuotaUsage { + storageQuota: SafeInt! +} + +type UserType { + """ + User avatar url + """ + avatarUrl: String + + copilot(workspaceId: String): Copilot! + + """ + User email verified + """ + createdAt: DateTime @deprecated(reason: "useless") + + """ + User email + """ + email: String! + + """ + User email verified + """ + emailVerified: Boolean! + + """ + Enabled features of a user + """ + features: [FeatureType!]! + + """ + User password has been set + """ + hasPassword: Boolean + + id: ID! + + """ + Get user invoice count + """ + invoiceCount: Int! + + invoices(skip: Int, take: Int = 8): [InvoiceType!]! + + """ + User name + """ + name: String! + + quota: UserQuota + + quotaUsage: UserQuotaUsage! + + subscriptions: [SubscriptionType!]! + + token: tokenType! + @deprecated(reason: "use [/api/auth/sign-in?native=true] instead") +} + +type VersionRejectedDataType { + serverVersion: String! + + version: String! +} + +type WorkspaceBlobSizes { + size: SafeInt! +} + +""" +Workspace invite link expire time +""" +enum WorkspaceInviteLinkExpireTime { + OneDay + + OneMonth + + OneWeek + + ThreeDays +} + +""" +Member invite status in workspace +""" +enum WorkspaceMemberStatus { + Accepted + + NeedMoreSeat + + NeedMoreSeatAndReview + + Pending + + UnderReview +} + +type WorkspacePage { + id: String! + + mode: PublicPageMode! + + public: Boolean! + + workspaceId: String! +} + +type WorkspacePageMeta { + createdAt: DateTime! + + createdBy: EditorType + + updatedAt: DateTime! + + updatedBy: EditorType +} + +type WorkspaceType { + """ + Available features of workspace + """ + availableFeatures: [FeatureType!]! + + """ + List blobs of workspace + """ + blobs: [ListedBlob!]! + + """ + Blobs size of workspace + """ + blobsSize: Int! + + """ + Workspace created date + """ + createdAt: DateTime! + + """ + Enable AI + """ + enableAi: Boolean! + + """ + Enable url previous when sharing + """ + enableUrlPreview: Boolean! + + """ + Enabled features of workspace + """ + features: [FeatureType!]! + + histories(before: DateTime, guid: String!, take: Int): [DocHistoryType!]! + + id: ID! + + """ + is current workspace initialized + """ + initialized: Boolean! + + """ + invite link for workspace + """ + inviteLink: InviteLink + + """ + Get user invoice count + """ + invoiceCount: Int! + + invoices(skip: Int, take: Int = 8): [InvoiceType!]! + + """ + member count of workspace + """ + memberCount: Int! + + """ + Members of workspace + """ + members(skip: Int, take: Int): [InviteUserType!]! + + """ + Owner of workspace + """ + owner: UserType! + + """ + Cloud page metadata of workspace + """ + pageMeta(pageId: String!): WorkspacePageMeta! + + """ + Permission of current signed in user in workspace + """ + permission: Permission! + + """ + is Public workspace + """ + public: Boolean! + + """ + Get public page of a workspace by page id. + """ + publicPage(pageId: String!): WorkspacePage + + """ + Public pages of a workspace + """ + publicPages: [WorkspacePage!]! + + """ + quota of workspace + """ + quota: QuotaQueryType! + + """ + Shared pages of workspace + """ + sharedPages: [String!]! @deprecated(reason: "use WorkspaceType.publicPages") + + """ + The team subscription of the workspace, if exists. + """ + subscription: SubscriptionType + + """ + if workspace is team workspace + """ + team: Boolean! +} + +type WrongSignInCredentialsDataType { + email: String! +} + +""" +A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. + +In some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor. +""" +type __Directive { + name: String! + + description: String + + isRepeatable: Boolean! + + locations: [__DirectiveLocation!]! + + args(includeDeprecated: Boolean = false): [__InputValue!]! +} + +""" +A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies. +""" +enum __DirectiveLocation { + """ + Location adjacent to a query operation. + """ + QUERY + + """ + Location adjacent to a mutation operation. + """ + MUTATION + + """ + Location adjacent to a subscription operation. + """ + SUBSCRIPTION + + """ + Location adjacent to a field. + """ + FIELD + + """ + Location adjacent to a fragment definition. + """ + FRAGMENT_DEFINITION + + """ + Location adjacent to a fragment spread. + """ + FRAGMENT_SPREAD + + """ + Location adjacent to an inline fragment. + """ + INLINE_FRAGMENT + + """ + Location adjacent to a variable definition. + """ + VARIABLE_DEFINITION + + """ + Location adjacent to a schema definition. + """ + SCHEMA + + """ + Location adjacent to a scalar definition. + """ + SCALAR + + """ + Location adjacent to an object type definition. + """ + OBJECT + + """ + Location adjacent to a field definition. + """ + FIELD_DEFINITION + + """ + Location adjacent to an argument definition. + """ + ARGUMENT_DEFINITION + + """ + Location adjacent to an interface definition. + """ + INTERFACE + + """ + Location adjacent to a union definition. + """ + UNION + + """ + Location adjacent to an enum definition. + """ + ENUM + + """ + Location adjacent to an enum value definition. + """ + ENUM_VALUE + + """ + Location adjacent to an input object type definition. + """ + INPUT_OBJECT + + """ + Location adjacent to an input object field definition. + """ + INPUT_FIELD_DEFINITION +} + +""" +One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string. +""" +type __EnumValue { + name: String! + + description: String + + isDeprecated: Boolean! + + deprecationReason: String +} + +""" +Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type. +""" +type __Field { + name: String! + + description: String + + args(includeDeprecated: Boolean = false): [__InputValue!]! + + type: __Type! + + isDeprecated: Boolean! + + deprecationReason: String +} + +""" +Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value. +""" +type __InputValue { + name: String! + + description: String + + type: __Type! + + """ + A GraphQL-formatted string representing the default value for this input value. + """ + defaultValue: String + + isDeprecated: Boolean! + + deprecationReason: String +} + +""" +A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations. +""" +type __Schema { + description: String + + """ + A list of all types supported by this server. + """ + types: [__Type!]! + + """ + The type that query operations will be rooted at. + """ + queryType: __Type! + + """ + If this server supports mutation, the type that mutation operations will be rooted at. + """ + mutationType: __Type + + """ + If this server support subscription, the type that subscription operations will be rooted at. + """ + subscriptionType: __Type + + """ + A list of all directives supported by this server. + """ + directives: [__Directive!]! +} + +""" +The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum. + +Depending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional `specifiedByURL`, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types. +""" +type __Type { + kind: __TypeKind! + + name: String + + description: String + + specifiedByURL: String + + fields(includeDeprecated: Boolean = false): [__Field!] + + interfaces: [__Type!] + + possibleTypes: [__Type!] + + enumValues(includeDeprecated: Boolean = false): [__EnumValue!] + + inputFields(includeDeprecated: Boolean = false): [__InputValue!] + + ofType: __Type + + isOneOf: Boolean +} + +""" +An enum describing what kind of type a given `__Type` is. +""" +enum __TypeKind { + """ + Indicates this type is a scalar. + """ + SCALAR + + """ + Indicates this type is an object. `fields` and `interfaces` are valid fields. + """ + OBJECT + + """ + Indicates this type is an interface. `fields`, `interfaces`, and `possibleTypes` are valid fields. + """ + INTERFACE + + """ + Indicates this type is a union. `possibleTypes` is a valid field. + """ + UNION + + """ + Indicates this type is an enum. `enumValues` is a valid field. + """ + ENUM + + """ + Indicates this type is an input object. `inputFields` is a valid field. + """ + INPUT_OBJECT + + """ + Indicates this type is a list. `ofType` is a valid field. + """ + LIST + + """ + Indicates this type is a non-null. `ofType` is a valid field. + """ + NON_NULL +} + +type tokenType { + refresh: String! + + sessionToken: String + + token: String! +} + +""" +Marks an element of a GraphQL schema as no longer supported. +""" +directive @deprecated( + "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/)." + reason: String = "No longer supported" +) on ARGUMENT_DEFINITION | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +""" +Directs the executor to include this field or fragment only when the `if` argument is true. +""" +directive @include( + "Included when true." + if: Boolean! +) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +""" +Indicates exactly one field must be supplied and this field must not be `null`. +""" +directive @oneOf on INPUT_OBJECT + +""" +Directs the executor to skip this field or fragment when the `if` argument is true. +""" +directive @skip( + "Skipped when true." + if: Boolean! +) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +""" +Exposes a URL that specifies the behavior of this scalar. +""" +directive @specifiedBy( + "The URL that specifies the behavior of this scalar." + url: String! +) on SCALAR + +schema { + query: Query + mutation: Mutation +} diff --git a/packages/frontend/apps/android/App/settings.gradle b/packages/frontend/apps/android/App/settings.gradle index 944c98f423..c00cf3aebe 100644 --- a/packages/frontend/apps/android/App/settings.gradle +++ b/packages/frontend/apps/android/App/settings.gradle @@ -1,5 +1,18 @@ -include ':app' -include ':capacitor-cordova-android-plugins' -project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') +pluginManagement { + repositories { + 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' diff --git a/packages/frontend/apps/android/App/variables.gradle b/packages/frontend/apps/android/App/variables.gradle index 3abba966db..5eb63f6959 100644 --- a/packages/frontend/apps/android/App/variables.gradle +++ b/packages/frontend/apps/android/App/variables.gradle @@ -2,15 +2,4 @@ ext { minSdkVersion = 22 compileSdkVersion = 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' -} +} \ No newline at end of file diff --git a/packages/frontend/apps/android/README.md b/packages/frontend/apps/android/README.md index 6d64273332..283eb33824 100644 --- a/packages/frontend/apps/android/README.md +++ b/packages/frontend/apps/android/README.md @@ -2,6 +2,17 @@ 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 - yarn install diff --git a/packages/frontend/apps/android/capacitor.config.ts b/packages/frontend/apps/android/capacitor.config.ts index 10b3e64199..f1c73e4311 100644 --- a/packages/frontend/apps/android/capacitor.config.ts +++ b/packages/frontend/apps/android/capacitor.config.ts @@ -16,6 +16,14 @@ const config: CapacitorConfig = { releaseType: 'AAB', }, }, + plugins: { + CapacitorHttp: { + enabled: true, + }, + CapacitorCookies: { + enabled: true, + }, + }, }; export default config; diff --git a/packages/frontend/apps/android/package.json b/packages/frontend/apps/android/package.json index 1dd529a2b2..4f04cb6b01 100644 --- a/packages/frontend/apps/android/package.json +++ b/packages/frontend/apps/android/package.json @@ -15,7 +15,9 @@ "@blocksuite/affine": "workspace:*", "@blocksuite/icons": "2.2.2", "@capacitor/android": "^7.0.0", + "@capacitor/app": "^7.0.0", "@capacitor/core": "^7.0.0", + "@capgo/inappbrowser": "^6.9.35", "@sentry/react": "^8.44.0", "@toeverything/infra": "workspace:*", "react": "^19.0.0", diff --git a/packages/frontend/apps/android/src/app.tsx b/packages/frontend/apps/android/src/app.tsx index 4ff63212a2..8e0c809632 100644 --- a/packages/frontend/apps/android/src/app.tsx +++ b/packages/frontend/apps/android/src/app.tsx @@ -3,15 +3,20 @@ import { AppFallback } from '@affine/core/mobile/components/app-fallback'; import { configureMobileModules } from '@affine/core/mobile/modules'; import { router } from '@affine/core/mobile/router'; import { configureCommonModules } from '@affine/core/modules'; +import { AuthService, DefaultServerService } from '@affine/core/modules/cloud'; import { I18nProvider } from '@affine/core/modules/i18n'; import { LifecycleService } from '@affine/core/modules/lifecycle'; import { configureLocalStorageStateStorageImpls, NbstoreProvider, } 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 { configureBrowserWorkspaceFlavours } from '@affine/core/modules/workspace-engine'; 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 { OpClient } from '@toeverything/infra/op'; import { Suspense } from 'react'; @@ -43,12 +48,62 @@ framework.impl(NbstoreProvider, { }); 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 window.addEventListener('focus', () => { frameworkProvider.get(LifecycleService).applicationFocus(); }); 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() { return ( diff --git a/yarn.lock b/yarn.lock index ed88bb3c7a..e5bcf39943 100644 --- a/yarn.lock +++ b/yarn.lock @@ -226,8 +226,10 @@ __metadata: "@blocksuite/affine": "workspace:*" "@blocksuite/icons": "npm:2.2.2" "@capacitor/android": "npm:^7.0.0" + "@capacitor/app": "npm:^7.0.0" "@capacitor/cli": "npm:^7.0.0" "@capacitor/core": "npm:^7.0.0" + "@capgo/inappbrowser": "npm:^6.9.35" "@sentry/react": "npm:^8.44.0" "@toeverything/infra": "workspace:*" "@types/react": "npm:^19.0.1" @@ -4261,6 +4263,15 @@ __metadata: languageName: node 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": version: 3.2.4 resolution: "@chromatic-com/storybook@npm:3.2.4"