From 5bf2975c0d1550c7a89fe7c5f1a8af6ad2468093 Mon Sep 17 00:00:00 2001 From: Simon Marquis Date: Sun, 25 Oct 2020 12:00:34 +0100 Subject: [PATCH] Kotlin migration and bug fixes --- .github/workflows/android.yml | 30 +++++ .travis.yml | 25 ----- app/build.gradle | 104 ----------------- app/build.gradle.kts | 106 ++++++++++++++++++ .../fr/smarquis/fcm/data/model/Message.kt | 2 + .../fr/smarquis/fcm/data/model/Payload.kt | 33 ++++-- .../fr/smarquis/fcm/view/ui/MainActivity.kt | 5 +- .../fcm/viewmodel/PresenceLiveData.kt | 4 +- build.gradle | 26 ----- build.gradle.kts | 26 +++++ gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle | 1 - settings.gradle.kts | 1 + 13 files changed, 194 insertions(+), 171 deletions(-) create mode 100644 .github/workflows/android.yml delete mode 100644 .travis.yml delete mode 100644 app/build.gradle create mode 100644 app/build.gradle.kts delete mode 100644 build.gradle create mode 100644 build.gradle.kts delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000..7d01366 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,30 @@ +name: Android CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Fix gradlew permission + run: chmod +x gradlew + - name: Check + run: ./gradlew check --stacktrace + - name: Assemble + run: ./gradlew assembleDebug --stacktrace + - name: Upload APK + uses: actions/upload-artifact@v2 + with: + name: app-debug.apk + path: app/build/outputs/apk/debug/app-debug.apk \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e497828..0000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -language: android -jdk: oraclejdk8 -before_cache: - - 'rm -f $HOME/.gradle/caches/modules-2/modules-2.lock' - - 'rm -fr $HOME/.gradle/caches/*/plugin-resolution/' -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ - - $HOME/.android/build-cache -android: - licenses: - - android-sdk-preview-license-.+ - - android-sdk-license-.+ - - google-gdk-license-.+ - components: - - tools - - platform-tools - - build-tools-29.0.2 - - android-29 - - extra -before_install: - - 'chmod +x gradlew' -script: - - './gradlew clean check --stacktrace' diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 54a10a9..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,104 +0,0 @@ -def versionMajor = 1 -def versionMinor = 8 -def versionPatch = 1 -def versionBuild = 0 - -apply plugin: 'com.android.application' - -apply plugin: 'kotlin-android' - -apply plugin: 'kotlin-android-extensions' - -apply plugin: 'kotlin-kapt' - -android { - compileSdkVersion 29 - buildToolsVersion '29.0.2' - defaultConfig { - applicationId "fr.smarquis.fcm" - minSdkVersion 16 - targetSdkVersion 29 - versionCode versionMajor * 1000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild - versionName "${versionMajor}.${versionMinor}.${versionPatch}" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - multiDexEnabled true - - javaCompileOptions { - annotationProcessorOptions { - arguments = [ - "room.schemaLocation" : "$projectDir/schemas".toString(), - "room.incremental" : "true", - "room.expandProjection": "true" - ] - } - } - } - buildTypes { - release { - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() - } - testOptions { - unitTests.includeAndroidResources = true - unitTests.returnDefaultValues = true - } -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - - /* AndroidX */ - implementation 'androidx.appcompat:appcompat:1.2.0-alpha02' - implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4' - implementation 'androidx.core:core-ktx:1.3.0-alpha01' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' - implementation 'androidx.preference:preference-ktx:1.1.0' - implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha01' - implementation 'androidx.transition:transition:1.3.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - - /* Material Design */ - implementation 'com.google.android.material:material:1.2.0-alpha05' - - /* Firebase */ - implementation 'com.google.firebase:firebase-core:17.2.2' - implementation 'com.google.firebase:firebase-messaging:20.1.0' - implementation 'com.google.firebase:firebase-database:19.2.1' - - /* Koin: Dependency Injection */ - implementation 'org.koin:koin-android:2.0.1' - implementation 'org.koin:koin-android-scope:2.0.1' - implementation 'org.koin:koin-android-viewmodel:2.0.1' - testImplementation 'org.koin:koin-test:2.0.1' - androidTestImplementation 'org.koin:koin-test:2.0.1' - - /* Moshi: JSON parsing */ - implementation 'com.squareup.moshi:moshi-adapters:1.9.2' - implementation 'com.squareup.moshi:moshi-kotlin:1.9.2' - kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.9.2' - - /* Room: SQLite persistence */ - implementation 'androidx.room:room-runtime:2.2.4' - kapt 'androidx.room:room-compiler:2.2.4' - implementation 'androidx.room:room-ktx:2.2.4' - testImplementation 'androidx.room:room-testing:2.2.4' - - /* Kotlin Coroutines */ - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3' - - /* JUnit */ - testImplementation 'junit:junit:4.13' - -} - -apply plugin: 'com.google.gms.google-services' diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..40d2c2d --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,106 @@ +plugins { + id("com.android.application") + kotlin("android") + kotlin("android.extensions") + kotlin("kapt") +} + +val versionMajor = 1 +val versionMinor = 8 +val versionPatch = 2 +val versionBuild = 0 + +android { + compileSdkVersion(30) + defaultConfig { + applicationId = "fr.smarquis.fcm" + minSdkVersion(16) + targetSdkVersion(30) + versionCode = versionMajor * 1000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild + versionName = "$versionMajor.$versionMinor.$versionPatch" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + multiDexEnabled = true + + javaCompileOptions { + annotationProcessorOptions { + argument("room.schemaLocation", "$projectDir/schemas") + argument("room.incremental", "true") + argument("room.expandProjection", "true") + } + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + testOptions { + unitTests.isIncludeAndroidResources = true + unitTests.isReturnDefaultValues = true + } +} + +tasks.withType { + kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.10") + + /* AndroidX */ + implementation("androidx.appcompat:appcompat:1.3.0-alpha02") + implementation("androidx.constraintlayout:constraintlayout:2.0.2") + implementation("androidx.core:core-ktx:1.5.0-alpha04") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.2.0") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0") + implementation("androidx.preference:preference-ktx:1.1.1") + implementation("androidx.recyclerview:recyclerview:1.2.0-alpha06") + implementation("androidx.transition:transition:1.3.1") + androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0") + androidTestImplementation("androidx.test.ext:junit:1.1.2") + + /* Material Design */ + implementation("com.google.android.material:material:1.3.0-alpha03") + + /* Firebase */ + implementation("com.google.firebase:firebase-core:17.5.1") + implementation("com.google.firebase:firebase-messaging:20.3.0") + implementation("com.google.firebase:firebase-database:19.5.1") + + /* Koin: Dependency Injection */ + val koin = "2.1.6" + implementation("org.koin:koin-android:$koin") + implementation("org.koin:koin-android-scope:$koin") + implementation("org.koin:koin-android-viewmodel:$koin") + testImplementation("org.koin:koin-test:$koin") + androidTestImplementation("org.koin:koin-test:$koin") + + /* Moshi: JSON parsing */ + val moshi = "1.11.0" + implementation("com.squareup.moshi:moshi-adapters:$moshi") + implementation("com.squareup.moshi:moshi-kotlin:$moshi") + kapt("com.squareup.moshi:moshi-kotlin-codegen:$moshi") + + /* Room: SQLite persistence */ + val room = "2.2.5" + implementation("androidx.room:room-runtime:$room") + kapt("androidx.room:room-compiler:$room") + implementation("androidx.room:room-ktx:$room") + testImplementation("androidx.room:room-testing:$room") + + /* Kotlin Coroutines */ + val coroutines = "1.4.0-M1" + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutines") + + /* JUnit */ + testImplementation("junit:junit:4.13.1") + +} + +apply(plugin = "com.google.gms.google-services") diff --git a/app/src/main/java/fr/smarquis/fcm/data/model/Message.kt b/app/src/main/java/fr/smarquis/fcm/data/model/Message.kt index f74227a..8483b90 100644 --- a/app/src/main/java/fr/smarquis/fcm/data/model/Message.kt +++ b/app/src/main/java/fr/smarquis/fcm/data/model/Message.kt @@ -1,10 +1,12 @@ package fr.smarquis.fcm.data.model +import androidx.annotation.Keep import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey @Entity +@Keep data class Message( @PrimaryKey @ColumnInfo(name = "messageId") val messageId: String, diff --git a/app/src/main/java/fr/smarquis/fcm/data/model/Payload.kt b/app/src/main/java/fr/smarquis/fcm/data/model/Payload.kt index d74b78f..087c963 100644 --- a/app/src/main/java/fr/smarquis/fcm/data/model/Payload.kt +++ b/app/src/main/java/fr/smarquis/fcm/data/model/Payload.kt @@ -10,12 +10,15 @@ import android.net.Uri import android.text.TextUtils import androidx.annotation.DrawableRes import androidx.annotation.IdRes +import androidx.annotation.Keep import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.Builder import androidx.core.text.bold import androidx.core.text.buildSpannedString import com.google.firebase.messaging.RemoteMessage import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.squareup.moshi.JsonDataException import com.squareup.moshi.Moshi import fr.smarquis.fcm.R import fr.smarquis.fcm.view.ui.CopyToClipboardActivity @@ -23,6 +26,7 @@ import org.koin.core.KoinComponent import org.koin.core.inject import java.io.IOException +@Keep sealed class Payload { @IdRes @@ -35,17 +39,19 @@ sealed class Payload { abstract fun configure(builder: Builder): Builder + @JsonClass(generateAdapter = true) data class App( @Json(name = "title") - private val title: String? = null, + internal val title: String? = null, @Json(name = "package") - private val packageName: String? = null + internal val packageName: String? = null ) : Payload() { override fun notificationId(): Int = R.id.notification_id_app override fun icon(): Int = R.drawable.ic_shop_24dp + @delegate:Transient private val display: CharSequence by lazy { buildSpannedString { bold { append("title: ") } @@ -83,20 +89,22 @@ sealed class Payload { } + @JsonClass(generateAdapter = true) data class Link( @Json(name = "title") - private val title: String? = null, + internal val title: String? = null, @Json(name = "url") - private val url: String? = null, + internal val url: String? = null, @Json(name = "open") @Deprecated(message = "Since Android 10, starting an Activity from background has been disabled https://developer.android.com/guide/components/activities/background-starts") - val open: Boolean = false + internal val open: Boolean = false ) : Payload() { override fun notificationId(): Int = R.id.notification_id_link override fun icon(): Int = R.drawable.ic_link_24dp + @delegate:Transient private val display: CharSequence by lazy { buildSpannedString { bold { append("title: ") } @@ -123,6 +131,7 @@ sealed class Payload { } @Suppress("CanSealedSubClassBeObject") + @JsonClass(generateAdapter = true) class Ping : Payload() { override fun notificationId(): Int = R.id.notification_id_ping @@ -140,19 +149,21 @@ sealed class Payload { } + @JsonClass(generateAdapter = true) data class Text( @Json(name = "title") - private val title: String? = null, + internal val title: String? = null, @Json(name = "message") - val text: String? = null, + internal val text: String? = null, @Json(name = "clipboard") - val clipboard: Boolean = false + internal val clipboard: Boolean = false ) : Payload() { override fun notificationId(): Int = R.id.notification_id_text override fun icon(): Int = R.drawable.ic_chat_24dp + @delegate:Transient private val display: CharSequence by lazy { buildSpannedString { bold { append("title: ") } @@ -178,15 +189,17 @@ sealed class Payload { } + @JsonClass(generateAdapter = true) data class Raw( @Json(name = "data") - private val data: Map? = null + internal val data: Map? = null ) : Payload(), KoinComponent { override fun notificationId(): Int = R.id.notification_id_raw override fun icon(): Int = R.drawable.ic_code_24dp + @delegate:Transient private val display: CharSequence by lazy { moshi.adapter>(MutableMap::class.java).indent(" ").toJson(data) } @@ -218,6 +231,8 @@ sealed class Payload { if (payload != null) { return payload } + } catch (e: JsonDataException) { + e.printStackTrace() } catch (e: IOException) { e.printStackTrace() } diff --git a/app/src/main/java/fr/smarquis/fcm/view/ui/MainActivity.kt b/app/src/main/java/fr/smarquis/fcm/view/ui/MainActivity.kt index a2ec174..ac1049a 100644 --- a/app/src/main/java/fr/smarquis/fcm/view/ui/MainActivity.kt +++ b/app/src/main/java/fr/smarquis/fcm/view/ui/MainActivity.kt @@ -33,7 +33,6 @@ import android.widget.Toast import android.widget.Toast.LENGTH_LONG import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.Observer import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar @@ -62,8 +61,8 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - viewModel.presence.observe(this, Observer { updatePresence(it) }) - viewModel.messages.observe(this, Observer { updateMessages(it) }) + viewModel.presence.observe(this, ::updatePresence) + viewModel.messages.observe(this, ::updateMessages) messagesAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { diff --git a/app/src/main/java/fr/smarquis/fcm/viewmodel/PresenceLiveData.kt b/app/src/main/java/fr/smarquis/fcm/viewmodel/PresenceLiveData.kt index b219dfd..8e6d946 100644 --- a/app/src/main/java/fr/smarquis/fcm/viewmodel/PresenceLiveData.kt +++ b/app/src/main/java/fr/smarquis/fcm/viewmodel/PresenceLiveData.kt @@ -4,7 +4,6 @@ import android.app.Application import android.os.Build.MANUFACTURER import android.os.Build.MODEL import androidx.lifecycle.LiveData -import com.google.android.gms.tasks.Tasks import com.google.firebase.database.* import com.google.firebase.iid.FirebaseInstanceId import fr.smarquis.fcm.data.model.Presence @@ -13,6 +12,7 @@ import fr.smarquis.fcm.utils.uuid import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await import kotlinx.coroutines.withContext import java.util.Locale.ROOT @@ -35,7 +35,7 @@ class PresenceLiveData(application: Application) : LiveData(Presence() fun fetchToken() = GlobalScope.launch(Dispatchers.Main) { val token = withContext(Dispatchers.IO) { try { - Tasks.await(instanceId.instanceId).token + instanceId.instanceId.await().token } catch (e: Exception) { e.message } diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 9dd7e05..0000000 --- a/build.gradle +++ /dev/null @@ -1,26 +0,0 @@ -apply plugin: "com.github.ben-manes.versions" - -buildscript { - ext.kotlin_version = '1.3.61' - repositories { - google() - jcenter() - } - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.android.tools.build:gradle:4.0.0-alpha09' - classpath 'com.google.gms:google-services:4.3.3' - classpath "com.github.ben-manes:gradle-versions-plugin:0.28.0" - } -} - -allprojects { - repositories { - google() - jcenter() - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..6351a8f --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,26 @@ +apply(plugin = "com.github.ben-manes.versions") + +buildscript { + repositories { + google() + jcenter() + + } + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10") + classpath("com.android.tools.build:gradle:4.2.0-alpha14") + classpath("com.google.gms:google-services:4.3.4") + classpath("com.github.ben-manes:gradle-versions-plugin:0.33.0") + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +tasks.register("clean", Delete::class) { + delete(rootProject.buildDir) +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7a71edf..b4ab6b0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index e7b4def..0000000 --- a/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':app' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..15a801b --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +include(":app")