diff --git a/.github/workflows/android-test.yml b/.github/workflows/android-test.yml
new file mode 100644
index 0000000..efd1db7
--- /dev/null
+++ b/.github/workflows/android-test.yml
@@ -0,0 +1,98 @@
+name: Android Emulator Tests
+on: [ push, pull_request ]
+
+jobs:
+ check-if-tests-exist:
+ runs-on: ubuntu-latest
+ outputs:
+ status: ${{ steps.check-androidTest.outputs.NOT_EMPTY }}
+ min-sdk-version: ${{ steps.get-sdk-version.outputs.MIN_SDK_VERSION }}
+ target-sdk-version: ${{ steps.get-sdk-version.outputs.TARGET_SDK_VERSION }}
+ app-id: ${{ steps.get-app-id.outputs.APP_ID }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ submodules: "recursive"
+ - name: Check if androidTest folder is not empty
+ run: |
+ echo "NOT_EMPTY=$([ "$(ls -A BackupApp/src/androidTest)" ] && echo 'true' || echo 'false')"
+ echo "NOT_EMPTY=$([ "$(ls -A BackupApp/src/androidTest)" ] && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT
+ id: check-androidTest
+ - name: Get min and target sdk
+ if: steps.check-androidTest.outputs.NOT_EMPTY == 'true'
+ id: get-sdk-version
+ run: |
+ echo "MIN_SDK_VERSION=$(cat BackupApp/build.gradle | grep minSdkVersion | rev | cut -d' ' -f 1 | rev)" >> $GITHUB_OUTPUT
+ echo "TARGET_SDK_VERSION=$(cat BackupApp/build.gradle | grep targetSdkVersion | rev | cut -d' ' -f 1 | rev)" >> $GITHUB_OUTPUT
+ - name: Get app ID
+ id: get-app-id
+ run: |
+ echo "APP_ID=$(cat BackupApp/build.gradle | grep applicationId | rev | cut -d' ' -f 1 | rev | tr -d '"')" >> $GITHUB_OUTPUT
+
+ test:
+ needs: check-if-tests-exist
+ if: needs.check-if-tests-exist.outputs.status == 'true'
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ api-level: [34, "${{ needs.check-if-tests-exist.outputs.min-sdk-version }}", "${{ needs.check-if-tests-exist.outputs.target-sdk-version }}"]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ submodules: 'recursive'
+
+ - name: Enable KVM group perms
+ run: |
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
+ sudo udevadm control --reload-rules
+ sudo udevadm trigger --name-match=kvm
+
+ - name: Gradle cache
+ uses: gradle/gradle-build-action@v3
+
+ - name: AVD cache
+ uses: actions/cache@v4
+ id: avd-cache
+ with:
+ path: |
+ ~/.android/avd/*
+ ~/.android/adb*
+ key: avd-${{ matrix.api-level }}
+
+ - name: Set up JDK environment
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'zulu'
+ java-version: 17
+
+ - name: create AVD and generate snapshot for caching
+ if: steps.avd-cache.outputs.cache-hit != 'true'
+ uses: reactivecircus/android-emulator-runner@v2
+ with:
+ api-level: ${{ matrix.api-level }}
+ target: ${{ matrix.api-level >= 30 && 'google_apis' || 'default' }}
+ arch: ${{ matrix.api-level < 21 && 'x86' || 'x86_64' }}
+ force-avd-creation: false
+ emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+ disable-animations: false
+ script: echo "Generated AVD snapshot for caching."
+
+ - name: Run connected tests
+ uses: ReactiveCircus/android-emulator-runner@v2
+ with:
+ api-level: ${{ matrix.api-level }}
+ target: ${{ matrix.api-level >= 30 && 'google_apis' || 'default' }}
+ arch: ${{ matrix.api-level < 21 && 'x86' || 'x86_64' }}
+ force-avd-creation: false
+ emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+ disable-animations: true
+ script: |
+ adb uninstall ${{needs.check-if-tests-exist.outputs.app-id}} || true
+ adb uninstall ${{needs.check-if-tests-exist.outputs.app-id}}.test || true
+ adb uninstall ${{needs.check-if-tests-exist.outputs.app-id}}.androidTest || true
+ ./gradlew :BackupApp:connectedCheck --stacktrace
+ adb uninstall ${{needs.check-if-tests-exist.outputs.app-id}} || true
+ adb uninstall ${{needs.check-if-tests-exist.outputs.app-id}}.test || true
+ adb uninstall ${{needs.check-if-tests-exist.outputs.app-id}}.androidTest || true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..9e60fe6
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,49 @@
+name: Continuous Integration
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ submodules: "recursive"
+
+ - name: Set up JDK environment
+ uses: actions/setup-java@v3
+ with:
+ distribution: "zulu"
+ java-version: 17
+
+ - name: Make gradlew executable
+ run: chmod +x ./gradlew
+
+ - name: Setup Gradle
+ uses: gradle/gradle-build-action@v2
+
+ - name: Run local unit tests
+ run: bash ./gradlew test --stacktrace
+
+ build:
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ submodules: "recursive"
+
+ - name: Set up JDK environment
+ uses: actions/setup-java@v3
+ with:
+ distribution: "zulu"
+ java-version: 17
+
+ - name: Make gradlew executable
+ run: chmod +x ./gradlew
+
+ - name: Setup Gradle
+ uses: gradle/gradle-build-action@v2
+
+ - name: Build the app
+ run: bash ./gradlew build --stacktrace
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
deleted file mode 100644
index b85bc32..0000000
--- a/.github/workflows/main.yml
+++ /dev/null
@@ -1,53 +0,0 @@
-name: Continuous integration
-
-on: [push, pull_request]
-
-jobs:
- test:
- name: Unit Tests
- runs-on: ubuntu-20.04
- steps:
- - uses: actions/checkout@v2
- with:
- submodules: 'recursive'
- - name: Make gradlew executable
- run: chmod +x ./gradlew
- - name: Run unit tests
- run: ./gradlew test
- - name: Android Test Report
- uses: asadmansr/android-test-report-action@v1.2.0
-
-#### These tests can not run because the api and another app needs to be installed
-# androidTest:
-# name: Instrumented Tests
-# runs-on: macOS-latest
-# steps:
-# - uses: actions/checkout@v2
-# with:
-# submodules: 'recursive'
-# - name: Set up JDK 1.8
-# uses: actions/setup-java@v1
-# with:
-# java-version: 1.8
-# - name: Make gradlew executable
-# run: chmod +x ./gradlew
-# - name: Run Instrumented Tests
-# uses: reactivecircus/android-emulator-runner@v1
-# with:
-# api-level: 29
-# arch: x86
-# disable-animations: true
-# script: ./gradlew connectedAndroidTest --stacktrace
-
- apk:
- name: Build APK
- runs-on: ubuntu-20.04
-
- steps:
- - uses: actions/checkout@v2
- with:
- submodules: 'recursive'
- - name: Make gradlew executable
- run: chmod +x ./gradlew
- - name: Build debug APK
- run: ./gradlew assembleDebug
diff --git a/.gitignore b/.gitignore
index 1f4cbd7..aa9c63f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,8 @@
*.iml
.gradle
/local.properties
-/.idea/caches
-/.idea/libraries
-/.idea/modules.xml
-/.idea/workspace.xml
-/.idea/navEditor.xml
-/.idea/assetWizardSettings.xml
+.idea/
+misc.xml
.DS_Store
/build
/captures
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
deleted file mode 100644
index 88ea3aa..0000000
--- a/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,122 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- xmlns:android
-
- ^$
-
-
-
-
-
-
-
-
- xmlns:.*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*:id
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:name
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- name
-
- ^$
-
-
-
-
-
-
-
-
- style
-
- ^$
-
-
-
-
-
-
-
-
- .*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*
-
- http://schemas.android.com/apk/res/android
-
-
- ANDROID_ATTRIBUTE_ORDER
-
-
-
-
-
-
- .*
-
- .*
-
-
- BY_NAME
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index 79ee123..0000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index b589d56..0000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
deleted file mode 100644
index 594e36a..0000000
--- a/.idea/gradle.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
deleted file mode 100644
index a5f05cd..0000000
--- a/.idea/jarRepositories.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index c7ab052..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 407beea..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/BackupAPI b/BackupAPI
index ac8e3a0..8687a8d 160000
--- a/BackupAPI
+++ b/BackupAPI
@@ -1 +1 @@
-Subproject commit ac8e3a0ca2df3b212815585bf55be0b187b21e71
+Subproject commit 8687a8dbd170866707dc41bcb879375400ef697a
diff --git a/BackupApp/build.gradle b/BackupApp/build.gradle
index 419d6f4..a787475 100644
--- a/BackupApp/build.gradle
+++ b/BackupApp/build.gradle
@@ -21,7 +21,6 @@ android {
compileSdk 34
defaultConfig {
- namespace "org.secuso.privacyfriendlybackup"
applicationId "org.secuso.privacyfriendlybackup"
minSdkVersion 21
targetSdkVersion 34
@@ -74,6 +73,10 @@ android {
jvmTarget = JavaVersion.VERSION_17.toString()
}
+ kotlin {
+ jvmToolchain(17)
+ }
+
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
@@ -85,22 +88,21 @@ android {
dependencies {
implementation project(path: ':BackupAPI')
- implementation project(path: ':jsonviewer')
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
- implementation 'androidx.core:core-ktx:1.8.0'
- implementation 'androidx.appcompat:appcompat:1.4.2'
+ implementation 'androidx.core:core-ktx:1.13.1'
+ implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
- implementation 'androidx.recyclerview:recyclerview:1.2.1'
- implementation 'com.google.android.material:material:1.6.1'
+ implementation 'androidx.recyclerview:recyclerview:1.3.2'
+ implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- testImplementation 'junit:junit:4.13.1'
- androidTestImplementation 'androidx.test.ext:junit:1.1.3'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
// OpenPGP API
implementation 'org.sufficientlysecure:openpgp-api:12.0'
@@ -113,25 +115,28 @@ dependencies {
testImplementation "androidx.work:work-testing:$work_version"
// Room Database
- def roomVersion = "2.5.1"
+ def roomVersion = "2.6.1"
implementation "androidx.room:room-runtime:$roomVersion"
annotationProcessor "androidx.room:room-compiler:$roomVersion"
kapt "androidx.room:room-compiler:$roomVersion"
implementation "androidx.room:room-ktx:$roomVersion"
// Lifecycle
- implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0"
- implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0'
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.2"
+ implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.2'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
- implementation 'androidx.lifecycle:lifecycle-common-java8:2.5.0'
+ implementation 'androidx.lifecycle:lifecycle-common-java8:2.8.2'
// Preferences
- def preference_version = "1.2.0"
+ def preference_version = "1.2.1"
implementation "androidx.preference:preference-ktx:$preference_version"
// Glide
implementation 'com.github.bumptech.glide:glide:4.11.0'
+ // JSON Viewer
+ implementation 'com.yuyh.json:jsonviewer:1.0.6'
+
// Retrofit for Google Drive Test
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
diff --git a/build.gradle b/build.gradle
index 109722e..7e98f85 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,12 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = "1.8.10"
+ ext.kotlin_version = "1.9.10"
repositories {
google()
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.4.2'
+ classpath 'com.android.tools.build:gradle:8.1.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
@@ -15,7 +15,7 @@ buildscript {
}
plugins {
- id 'com.google.devtools.ksp' version '1.8.10-1.0.9' apply false
+ id 'com.google.devtools.ksp' version "$kotlin_version-1.0.13" apply false
}
diff --git a/gradle.properties b/gradle.properties
index 4d15d01..fde9fa4 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -18,4 +18,7 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
-kotlin.code.style=official
\ No newline at end of file
+kotlin.code.style=official
+android.defaults.buildfeatures.buildconfig=true
+android.nonTransitiveRClass=false
+android.nonFinalResIds=false
\ No newline at end of file
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
diff --git a/libs/jsonviewer/.gitignore b/libs/jsonviewer/.gitignore
deleted file mode 100644
index 796b96d..0000000
--- a/libs/jsonviewer/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/libs/jsonviewer/build.gradle b/libs/jsonviewer/build.gradle
deleted file mode 100644
index e8a321d..0000000
--- a/libs/jsonviewer/build.gradle
+++ /dev/null
@@ -1,33 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 30
- resourcePrefix "jsonviewer"
-
- defaultConfig {
- minSdkVersion 14
- targetSdkVersion 30
- versionCode 6
- versionName "1.0.6"
- }
-
- buildTypes {
- release {
- postprocessing {
- removeUnusedCode false
- removeUnusedResources false
- obfuscate false
- optimizeCode false
- proguardFile 'proguard-rules.pro'
- }
- }
- }
-
-}
-
-dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
-
- implementation 'androidx.appcompat:appcompat:1.2.0'
- implementation 'androidx.recyclerview:recyclerview:1.1.0'
-}
diff --git a/libs/jsonviewer/proguard-rules.pro b/libs/jsonviewer/proguard-rules.pro
deleted file mode 100644
index f1b4245..0000000
--- a/libs/jsonviewer/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
diff --git a/libs/jsonviewer/src/main/AndroidManifest.xml b/libs/jsonviewer/src/main/AndroidManifest.xml
deleted file mode 100644
index 7e0335f..0000000
--- a/libs/jsonviewer/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/JsonRecyclerView.java b/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/JsonRecyclerView.java
deleted file mode 100644
index 99515d8..0000000
--- a/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/JsonRecyclerView.java
+++ /dev/null
@@ -1,194 +0,0 @@
-package com.yuyh.jsonviewer.library;
-
-import android.content.Context;
-import android.text.style.ForegroundColorSpan;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.yuyh.jsonviewer.library.adapter.BaseJsonViewerAdapter;
-import com.yuyh.jsonviewer.library.adapter.JsonViewerAdapter;
-import com.yuyh.jsonviewer.library.view.JsonItemView;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-/**
- * Created by yuyuhang on 2017/11/30.
- */
-public class JsonRecyclerView extends RecyclerView {
-
- private BaseJsonViewerAdapter mAdapter;
-
- public JsonRecyclerView(Context context) {
- this(context, null);
- }
-
- public JsonRecyclerView(Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public JsonRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- initView();
- }
-
- private void initView() {
- setLayoutManager(new LinearLayoutManager(getContext()));
- }
-
- public void bindJson(String jsonStr) {
- mAdapter = null;
- mAdapter = new JsonViewerAdapter(jsonStr);
- setAdapter(mAdapter);
- }
-
- public void bindJson(JSONArray array) {
- mAdapter = null;
- mAdapter = new JsonViewerAdapter(array);
- setAdapter(mAdapter);
- }
-
- public void bindJson(JSONObject object) {
- mAdapter = null;
- mAdapter = new JsonViewerAdapter(object);
- setAdapter(mAdapter);
- }
-
- public void setKeyColor(int color) {
- BaseJsonViewerAdapter.KEY_COLOR = color;
- }
-
- public void setValueTextColor(int color) {
- BaseJsonViewerAdapter.TEXT_COLOR = color;
- }
-
- public void setValueNumberColor(int color) {
- BaseJsonViewerAdapter.NUMBER_COLOR = color;
- }
-
- public void setValueBooleanColor(int color) {
- BaseJsonViewerAdapter.BOOLEAN_COLOR = color;
- }
-
- public void setValueUrlColor(int color) {
- BaseJsonViewerAdapter.URL_COLOR = color;
- }
-
- public void setValueNullColor(int color) {
- BaseJsonViewerAdapter.NUMBER_COLOR = color;
- }
-
- public void setBracesColor(int color) {
- BaseJsonViewerAdapter.BRACES_COLOR = color;
- }
-
- public void setTextSize(float sizeDP) {
- if (sizeDP < 10) {
- sizeDP = 10;
- } else if (sizeDP > 30) {
- sizeDP = 30;
- }
-
- if (BaseJsonViewerAdapter.TEXT_SIZE_DP != sizeDP) {
- BaseJsonViewerAdapter.TEXT_SIZE_DP = sizeDP;
- if (mAdapter != null) {
- updateAll(sizeDP);
- }
- }
- }
-
- public void setScaleEnable(boolean enable) {
- if (enable) {
- addOnItemTouchListener(touchListener);
- } else {
- removeOnItemTouchListener(touchListener);
- }
- }
-
- public void updateAll(float textSize) {
- LayoutManager manager = getLayoutManager();
-
- int count = manager.getChildCount();
-
- for (int i = 0; i < count; i++) {
- View view = manager.getChildAt(i);
- loop(view, textSize);
- }
- }
-
- private void loop(View view, float textSize) {
- if (view instanceof JsonItemView) {
- JsonItemView group = (JsonItemView) view;
-
- group.setTextSize(textSize);
-
- int childCount = group.getChildCount();
-
- for (int i = 0; i < childCount; i++) {
- View view1 = group.getChildAt(i);
- loop(view1, textSize);
- }
- }
- }
-
- int mode;
- float oldDist;
-
- private void zoom(float f) {
- setTextSize(BaseJsonViewerAdapter.TEXT_SIZE_DP * f);
- }
-
- private float spacing(MotionEvent event) {
- float x = event.getX(0) - event.getX(1);
- float y = event.getY(0) - event.getY(1);
- return (float) Math.sqrt(x * x + y * y);
- }
-
- private OnItemTouchListener touchListener = new OnItemTouchListener() {
- @Override
- public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent event) {
- switch (event.getAction() & event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mode = 1;
- break;
- case MotionEvent.ACTION_UP:
- mode = 0;
- break;
- case MotionEvent.ACTION_POINTER_UP:
- mode -= 1;
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- oldDist = spacing(event);
- mode += 1;
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (mode >= 2) {
- float newDist = spacing(event);
- if (Math.abs(newDist - oldDist) > 0.5f) {
- zoom(newDist / oldDist);
- oldDist = newDist;
- }
- }
- break;
- }
- return false;
- }
-
- @Override
- public void onTouchEvent(RecyclerView rv, MotionEvent event) {
-
- }
-
- @Override
- public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
-
- }
- };
-}
diff --git a/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/adapter/BaseJsonViewerAdapter.java b/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/adapter/BaseJsonViewerAdapter.java
deleted file mode 100644
index 3554528..0000000
--- a/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/adapter/BaseJsonViewerAdapter.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.yuyh.jsonviewer.library.adapter;
-
-import android.text.style.ForegroundColorSpan;
-import androidx.recyclerview.widget.RecyclerView;
-
-/**
- * Created by yuyuhang on 2017/11/30.
- */
-public abstract class BaseJsonViewerAdapter extends RecyclerView.Adapter {
-
- public static int KEY_COLOR = 0xFF922799;
- public static int TEXT_COLOR = 0xFF3AB54A;
- public static int NUMBER_COLOR = 0xFF25AAE2;
- public static int BOOLEAN_COLOR = 0xFFF98280;
- public static int URL_COLOR = 0xFF66D2D5;
- public static int NULL_COLOR = 0xFFEF5935;
- public static int BRACES_COLOR = 0xFF4A555F;
-
- public static float TEXT_SIZE_DP = 12;
-}
diff --git a/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/adapter/JsonViewerAdapter.java b/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/adapter/JsonViewerAdapter.java
deleted file mode 100644
index 520268a..0000000
--- a/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/adapter/JsonViewerAdapter.java
+++ /dev/null
@@ -1,287 +0,0 @@
-package com.yuyh.jsonviewer.library.adapter;
-
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.style.ForegroundColorSpan;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.yuyh.jsonviewer.library.utils.Utils;
-import com.yuyh.jsonviewer.library.view.JsonItemView;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONTokener;
-
-/**
- * Created by yuyuhang on 2017/11/29.
- */
-public class JsonViewerAdapter extends BaseJsonViewerAdapter {
-
- private String jsonStr;
-
- private JSONObject mJSONObject;
- private JSONArray mJSONArray;
-
- public JsonViewerAdapter(String jsonStr) {
- this.jsonStr = jsonStr;
-
- Object object = null;
- try {
- object = new JSONTokener(jsonStr).nextValue();
- } catch (JSONException e) {
- e.printStackTrace();
- }
- if (object instanceof JSONObject) {
- mJSONObject = (JSONObject) object;
- } else if (object instanceof JSONArray) {
- mJSONArray = (JSONArray) object;
- } else {
- throw new IllegalArgumentException("jsonStr is illegal.");
- }
- }
-
- public JsonViewerAdapter(JSONObject jsonObject) {
- this.mJSONObject = jsonObject;
- if (mJSONObject == null) {
- throw new IllegalArgumentException("jsonObject can not be null.");
- }
- }
-
- public JsonViewerAdapter(JSONArray jsonArray) {
- this.mJSONArray = jsonArray;
- if (mJSONArray == null) {
- throw new IllegalArgumentException("jsonArray can not be null.");
- }
- }
-
- @Override
- public JsonItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- return new JsonItemViewHolder(new JsonItemView(parent.getContext()));
- }
-
- @Override
- public void onBindViewHolder(JsonItemViewHolder holder, int position) {
- JsonItemView itemView = holder.itemView;
- itemView.setTextSize(TEXT_SIZE_DP);
- itemView.setRightColor(BRACES_COLOR);
- if (mJSONObject != null) {
- if (position == 0) {
- itemView.hideLeft();
- itemView.hideIcon();
- itemView.showRight("{");
- return;
- } else if (position == getItemCount() - 1) {
- itemView.hideLeft();
- itemView.hideIcon();
- itemView.showRight("}");
- return;
- } else if (mJSONObject.names() == null) {
- return;
- }
-
- String key = mJSONObject.names().optString(position - 1); // 遍历key
- Object value = mJSONObject.opt(key);
- if (position < getItemCount() - 2) {
- handleJsonObject(key, value, itemView, true, 1);
- } else {
- handleJsonObject(key, value, itemView, false, 1); // 最后一组,结尾不需要逗号
- }
- }
-
- if (mJSONArray != null) {
- if (position == 0) {
- itemView.hideLeft();
- itemView.hideIcon();
- itemView.showRight("[");
- return;
- } else if (position == getItemCount() - 1) {
- itemView.hideLeft();
- itemView.hideIcon();
- itemView.showRight("]");
- return;
- }
-
- Object value = mJSONArray.opt(position - 1); // 遍历array
- if (position < getItemCount() - 2) {
- handleJsonArray(value, itemView, true, 1);
- } else {
- handleJsonArray(value, itemView, false, 1); // 最后一组,结尾不需要逗号
- }
- }
- }
-
- @Override
- public int getItemCount() {
- if (mJSONObject != null) {
- if (mJSONObject.names() != null) {
- return mJSONObject.names().length() + 2;
- } else {
- return 2;
- }
- }
- if (mJSONArray != null) {
- return mJSONArray.length() + 2;
- }
- return 0;
- }
-
- /**
- * 处理 value 上级为 JsonObject 的情况,value有key
- *
- * @param value
- * @param key
- * @param itemView
- * @param appendComma
- * @param hierarchy
- */
- private void handleJsonObject(String key, Object value, JsonItemView itemView, boolean appendComma, int hierarchy) {
- SpannableStringBuilder keyBuilder = new SpannableStringBuilder(Utils.getHierarchyStr(hierarchy));
- keyBuilder.append("\"").append(key).append("\"").append(":");
- keyBuilder.setSpan(new ForegroundColorSpan(KEY_COLOR), 0, keyBuilder.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- keyBuilder.setSpan(new ForegroundColorSpan(BRACES_COLOR), keyBuilder.length() - 1, keyBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-
- itemView.showLeft(keyBuilder);
-
- handleValue(value, itemView, appendComma, hierarchy);
- }
-
- /**
- * 处理 value 上级为 JsonArray 的情况,value无key
- *
- * @param value
- * @param itemView
- * @param appendComma 结尾是否需要逗号(最后一组 value 不需要逗号)
- * @param hierarchy 缩进层级
- */
- private void handleJsonArray(Object value, JsonItemView itemView, boolean appendComma, int hierarchy) {
- itemView.showLeft(new SpannableStringBuilder(Utils.getHierarchyStr(hierarchy)));
-
- handleValue(value, itemView, appendComma, hierarchy);
- }
-
- /**
- * @param value
- * @param itemView
- * @param appendComma 结尾是否需要逗号(最后一组 key:value 不需要逗号)
- * @param hierarchy 缩进层级
- */
- private void handleValue(Object value, JsonItemView itemView, boolean appendComma, int hierarchy) {
- SpannableStringBuilder valueBuilder = new SpannableStringBuilder();
- if (value instanceof Number) {
- valueBuilder.append(value.toString());
- valueBuilder.setSpan(new ForegroundColorSpan(NUMBER_COLOR), 0, valueBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- } else if (value instanceof Boolean) {
- valueBuilder.append(value.toString());
- valueBuilder.setSpan(new ForegroundColorSpan(BOOLEAN_COLOR), 0, valueBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- } else if (value instanceof JSONObject) {
- itemView.showIcon(true);
- valueBuilder.append("Object{...}");
- valueBuilder.setSpan(new ForegroundColorSpan(BRACES_COLOR), 0, valueBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- itemView.setIconClickListener(new JsonItemClickListener(value, itemView, appendComma, hierarchy + 1));
- } else if (value instanceof JSONArray) {
- itemView.showIcon(true);
- valueBuilder.append("Array[").append(String.valueOf(((JSONArray) value).length())).append("]");
- int len = valueBuilder.length();
- valueBuilder.setSpan(new ForegroundColorSpan(BRACES_COLOR), 0, 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- valueBuilder.setSpan(new ForegroundColorSpan(NUMBER_COLOR), 6, len - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- valueBuilder.setSpan(new ForegroundColorSpan(BRACES_COLOR), len - 1, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- itemView.setIconClickListener(new JsonItemClickListener(value, itemView, appendComma, hierarchy + 1));
- } else if (value instanceof String) {
- itemView.hideIcon();
- valueBuilder.append("\"").append(value.toString()).append("\"");
- if (Utils.isUrl(value.toString())) {
- valueBuilder.setSpan(new ForegroundColorSpan(TEXT_COLOR), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- valueBuilder.setSpan(new ForegroundColorSpan(URL_COLOR), 1, valueBuilder.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- valueBuilder.setSpan(new ForegroundColorSpan(TEXT_COLOR), valueBuilder.length() - 1, valueBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- } else {
- valueBuilder.setSpan(new ForegroundColorSpan(TEXT_COLOR), 0, valueBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- } else if (valueBuilder.length() == 0 || value == null) {
- itemView.hideIcon();
- valueBuilder.append("null");
- valueBuilder.setSpan(new ForegroundColorSpan(NULL_COLOR), 0, valueBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- if (appendComma) {
- valueBuilder.append(",");
- }
-
- itemView.showRight(valueBuilder);
- }
-
- class JsonItemClickListener implements View.OnClickListener {
-
- private Object value;
- private JsonItemView itemView;
- private boolean appendComma;
- private int hierarchy;
-
- private boolean isCollapsed = true;
- private boolean isJsonArray;
-
- JsonItemClickListener(Object value, JsonItemView itemView, boolean appendComma, int hierarchy) {
- this.value = value;
- this.itemView = itemView;
- this.appendComma = appendComma;
- this.hierarchy = hierarchy;
- this.isJsonArray = value != null && value instanceof JSONArray;
- }
-
- @Override
- public void onClick(View view) {
- if (itemView.getChildCount() == 1) { // 初始(折叠) --> 展开""
- isCollapsed = false;
- itemView.showIcon(false);
- itemView.setTag(itemView.getRightText());
- itemView.showRight(isJsonArray ? "[" : "{");
- JSONArray array = isJsonArray ? (JSONArray) value : ((JSONObject) value).names();
- for (int i = 0; array != null && i < array.length(); i++) {
- JsonItemView childItemView = new JsonItemView(itemView.getContext());
- childItemView.setTextSize(TEXT_SIZE_DP);
- childItemView.setRightColor(BRACES_COLOR);
- Object childValue = array.opt(i);
- if (isJsonArray) {
- handleJsonArray(childValue, childItemView, i < array.length() - 1, hierarchy);
- } else {
- handleJsonObject((String) childValue, ((JSONObject) value).opt((String) childValue), childItemView, i < array.length() - 1, hierarchy);
- }
- itemView.addViewNoInvalidate(childItemView);
- }
-
- JsonItemView childItemView = new JsonItemView(itemView.getContext());
- childItemView.setTextSize(TEXT_SIZE_DP);
- childItemView.setRightColor(BRACES_COLOR);
- StringBuilder builder = new StringBuilder(Utils.getHierarchyStr(hierarchy - 1));
- builder.append(isJsonArray ? "]" : "}").append(appendComma ? "," : "");
- childItemView.showRight(builder);
- itemView.addViewNoInvalidate(childItemView);
- itemView.requestLayout();
- itemView.invalidate();
- } else { // 折叠 <--> 展开
- CharSequence temp = itemView.getRightText();
- itemView.showRight((CharSequence) itemView.getTag());
- itemView.setTag(temp);
- itemView.showIcon(!isCollapsed);
- for (int i = 1; i < itemView.getChildCount(); i++) {
- itemView.getChildAt(i).setVisibility(isCollapsed ? View.VISIBLE : View.GONE);
- }
- isCollapsed = !isCollapsed;
- }
- }
- }
-
- class JsonItemViewHolder extends RecyclerView.ViewHolder {
-
- JsonItemView itemView;
-
- JsonItemViewHolder(JsonItemView itemView) {
- super(itemView);
- setIsRecyclable(false);
- this.itemView = itemView;
- }
- }
-}
diff --git a/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/utils/Utils.java b/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/utils/Utils.java
deleted file mode 100644
index 1cc59bd..0000000
--- a/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/utils/Utils.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package com.yuyh.jsonviewer.library.utils;
-
-import java.util.regex.Pattern;
-
-/**
- * Created by yuyuhang on 2017/11/30.
- */
-public class Utils {
-
- private static Pattern urlPattern = Pattern.compile("^((https|http|ftp|rtsp|mms)?://)"
- + "?(([0-9a-z_!~*'().&=+$%-]+: )?[0-9a-z_!~*'().&=+$%-]+@)?" //ftp的user@
- + "(([0-9]{1,3}\\.){3}[0-9]{1,3}" // IP形式的URL- 199.194.52.184
- + "|" // 允许IP和DOMAIN(域名)
- + "([0-9a-z_!~*'()-]+\\.)*" // 域名- www.
- + "([0-9a-z][0-9a-z-]{0,61})?[0-9a-z]\\." // 二级域名
- + "[a-z]{2,6})" // first level domain- .com or .museum
- + "(:[0-9]{1,4})?" // 端口- :80
- + "((/?)|" // a slash isn't required if there is no file name
- + "(/[0-9a-z_!~*'().;?:@&=+$,%#-]+)+/?)$");
-
- /**
- * 判断字符串是否是url
- *
- * @param str
- * @return
- */
- public static boolean isUrl(String str) {
- return urlPattern.matcher(str).matches();
- }
-
- /**
- * json 格式化缩进(格式化前不能有缩进,最好是格式化从服务端下发的)
- *
- * @param jsonStr
- * @return
- */
- public static String jsonFormat(String jsonStr) {
- if (jsonStr == null) return "";
- int level = 0;
- StringBuilder builder = new StringBuilder();
- for (int i = 0; i < jsonStr.length(); i++) {
- char c = jsonStr.charAt(i);
- if (level > 0 && '\n' == builder.charAt(builder.length() - 1)) {
- builder.append(getHierarchyStr(level));
- }
- switch (c) {
- case '{':
- case '[':
- builder.append(c).append("\n");
- level++;
- break;
- case ',':
- builder.append(c).append("\n");
- break;
- case '}':
- case ']':
- builder.append("\n");
- level--;
- builder.append(getHierarchyStr(level));
- builder.append(c);
- break;
- default:
- builder.append(c);
- break;
- }
- }
-
- return builder.toString();
- }
-
- /**
- * 对应层级前面所需的空格数
- *
- * @param hierarchy 缩进层级
- * @return
- */
- public static String getHierarchyStr(int hierarchy) {
- StringBuilder levelStr = new StringBuilder();
- for (int levelI = 0; levelI < hierarchy; levelI++) {
- levelStr.append(" ");
- }
- return levelStr.toString();
- }
-
-}
diff --git a/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/view/JsonItemView.java b/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/view/JsonItemView.java
deleted file mode 100644
index 05e6f26..0000000
--- a/libs/jsonviewer/src/main/java/com/yuyh/jsonviewer/library/view/JsonItemView.java
+++ /dev/null
@@ -1,133 +0,0 @@
-package com.yuyh.jsonviewer.library.view;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.yuyh.jsonviewer.library.R;
-import com.yuyh.jsonviewer.library.adapter.BaseJsonViewerAdapter;
-
-/**
- * Created by yuyuhang on 2017/11/29.
- */
-public class JsonItemView extends LinearLayout {
-
- public static int TEXT_SIZE_DP = 12;
-
- private Context mContext;
-
- private TextView mTvLeft, mTvRight;
- private ImageView mIvIcon;
-
- public JsonItemView(Context context) {
- this(context, null);
- }
-
- public JsonItemView(Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public JsonItemView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
-
- mContext = context;
-
- initView();
- }
-
- private void initView() {
- setOrientation(VERTICAL);
- LayoutInflater.from(mContext).inflate(R.layout.jsonviewer_layout_item_view, this, true);
-
- mTvLeft = findViewById(R.id.tv_left);
- mTvRight = findViewById(R.id.tv_right);
- mIvIcon = findViewById(R.id.iv_icon);
- }
-
- public void setTextSize(float textSizeDp) {
- if (textSizeDp < 12) {
- textSizeDp = 12;
- } else if (textSizeDp > 30) {
- textSizeDp = 30;
- }
-
- TEXT_SIZE_DP = (int) textSizeDp;
-
- mTvLeft.setTextSize(TEXT_SIZE_DP);
- mTvRight.setTextSize(TEXT_SIZE_DP);
- mTvRight.setTextColor(BaseJsonViewerAdapter.BRACES_COLOR);
-
- // align the vertically expand/collapse icon to the text
- int textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, TEXT_SIZE_DP, getResources().getDisplayMetrics());
-
- LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mIvIcon.getLayoutParams();
- layoutParams.height = textSize;
- layoutParams.width = textSize;
- layoutParams.topMargin = textSize / 5;
-
- mIvIcon.setLayoutParams(layoutParams);
- }
-
- public void setRightColor(int color) {
- mTvRight.setTextColor(color);
- }
-
- public void hideLeft() {
- mTvLeft.setVisibility(GONE);
- }
-
- public void showLeft(CharSequence text) {
- mTvLeft.setVisibility(VISIBLE);
- if (text != null) {
- mTvLeft.setText(text);
- }
- }
-
- public void hideRight() {
- mTvRight.setVisibility(GONE);
- }
-
- public void showRight(CharSequence text) {
- mTvRight.setVisibility(VISIBLE);
- if (text != null) {
- mTvRight.setText(text);
- }
- }
-
- public CharSequence getRightText() {
- return mTvRight.getText();
- }
-
- public void hideIcon() {
- mIvIcon.setVisibility(GONE);
- }
-
- public void showIcon(boolean isPlus) {
- mIvIcon.setVisibility(VISIBLE);
- mIvIcon.setImageResource(isPlus ? R.drawable.jsonviewer_plus : R.drawable.jsonviewer_minus);
- mIvIcon.setContentDescription(getResources().getString(isPlus ? R.string.jsonViewer_icon_plus : R.string.jsonViewer_icon_minus));
- }
-
- public void setIconClickListener(OnClickListener listener) {
- mIvIcon.setOnClickListener(listener);
- }
-
- public void addViewNoInvalidate(View child) {
- ViewGroup.LayoutParams params = child.getLayoutParams();
- if (params == null) {
- params = generateDefaultLayoutParams();
- if (params == null) {
- throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
- }
- }
- addViewInLayout(child, -1, params);
- }
-}
diff --git a/libs/jsonviewer/src/main/res/drawable/jsonviewer_minus.xml b/libs/jsonviewer/src/main/res/drawable/jsonviewer_minus.xml
deleted file mode 100644
index d7ec09a..0000000
--- a/libs/jsonviewer/src/main/res/drawable/jsonviewer_minus.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- -
-
-
-
-
-
\ No newline at end of file
diff --git a/libs/jsonviewer/src/main/res/drawable/jsonviewer_plus.xml b/libs/jsonviewer/src/main/res/drawable/jsonviewer_plus.xml
deleted file mode 100644
index b41fc31..0000000
--- a/libs/jsonviewer/src/main/res/drawable/jsonviewer_plus.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
- -
-
-
-
-
- -
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/libs/jsonviewer/src/main/res/layout/jsonviewer_layout_item_view.xml b/libs/jsonviewer/src/main/res/layout/jsonviewer_layout_item_view.xml
deleted file mode 100644
index 4887a0e..0000000
--- a/libs/jsonviewer/src/main/res/layout/jsonviewer_layout_item_view.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/libs/jsonviewer/src/main/res/values/strings.xml b/libs/jsonviewer/src/main/res/values/strings.xml
deleted file mode 100644
index c2e6692..0000000
--- a/libs/jsonviewer/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
- expand
- collapse
-
diff --git a/settings.gradle b/settings.gradle
index 92b831f..13d6585 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -2,7 +2,4 @@ include ':BackupApp'
rootProject.name = "Backup"
include ':BackupAPI'
-project(':BackupAPI').projectDir = new File('BackupAPI/BackupAPI')
-
-include ':jsonviewer'
-project(':jsonviewer').projectDir = new File('libs/jsonviewer')
\ No newline at end of file
+project(':BackupAPI').projectDir = new File('BackupAPI/BackupAPI')
\ No newline at end of file