From 53df33a0ca8764c5a21916dcc1d4d83097673473 Mon Sep 17 00:00:00 2001 From: Nicholas Doglio Date: Sun, 12 Jan 2020 18:56:47 -0500 Subject: [PATCH] Major cleanup: - Move from regular ktlint to ktlint gradle plugin - Update licenses to 2020 - Wrote more tests - Added DaggerReflect - Moved from buildSrcVersions to Ben Manes Version plugin - Fragment constructor injection - Added timestamps to notes - Added some sample JSON --- .github/workflows/android.yml | 2 +- app/build.gradle.kts | 126 +++--- app/sampledata/about.json | 14 + app/sampledata/notes.json | 69 ++++ app/src/main/AndroidManifest.xml | 6 +- app/src/main/assets/open_source_licenses.html | 331 +++++---------- .../nicholasdoglio/notes/NotesApplication.kt | 13 +- .../notes/data/local/AboutDataStore.kt | 6 +- .../data/local/TimestampColumnAdapter.kt | 32 +- .../notes/data/model/AboutItem.kt | 2 +- .../notes/data/repo/NoteRepository.kt | 31 +- .../notes/data/repo/Repository.kt | 3 +- .../nicholasdoglio/notes/di/AppComponent.kt | 7 +- ...FragmentBinding.kt => BindingFragments.kt} | 10 +- .../nicholasdoglio/notes/di/BindingModule.kt | 8 +- ...ewModelBinding.kt => BindingViewModels.kt} | 8 +- .../nicholasdoglio/notes/di/DatabaseModule.kt | 11 +- .../notes/di/NotesFragmentFactory.kt | 2 +- .../notes/di/NotesViewModelFactory.kt | 2 +- .../InjectableNavHostFragment.kt | 4 +- .../notes/{ui => features}/MainActivity.kt | 4 +- .../{ui => features}/about/AboutAdapter.kt | 10 +- .../{ui => features}/about/AboutFragment.kt | 14 +- .../about/AboutItemViewHolder.kt | 30 +- .../{ui => features}/about/AboutViewModel.kt | 8 +- .../{ui => features}/about/LibsFragment.kt | 4 +- .../editnote}/DiscardFragment.kt | 4 +- .../note => features/editnote}/NoteAction.kt | 4 +- .../editnote}/NoteFragment.kt | 4 +- .../editnote}/NoteViewModel.kt | 6 +- .../{ui => features}/list/NoteListAdapter.kt | 24 +- .../{ui => features}/list/NoteListFragment.kt | 63 ++- .../list/NoteListViewModel.kt | 4 +- .../{ui => features}/list/NoteViewHolder.kt | 26 +- .../{ui => features}/tile/CreateNoteTile.kt | 6 +- .../nicholasdoglio/notes/util/Extensions.kt | 10 +- .../notes/util/SchedulersProvider.kt | 6 +- .../drawable-v24/ic_launcher_foreground.xml | 2 +- app/src/main/res/drawable/ic_edit.xml | 2 +- .../main/res/drawable/ic_launch_screen.xml | 2 +- .../res/drawable/ic_launcher_background.xml | 2 +- app/src/main/res/drawable/launch_screen.xml | 2 +- app/src/main/res/layout/activity_main.xml | 4 +- app/src/main/res/layout/fragment_about.xml | 20 +- app/src/main/res/layout/fragment_note.xml | 28 +- .../main/res/layout/fragment_note_list.xml | 71 ++-- app/src/main/res/layout/item_about.xml | 6 +- app/src/main/res/layout/item_note.xml | 12 +- app/src/main/res/menu/list_menu.xml | 2 +- app/src/main/res/navigation/nav_graph.xml | 18 +- app/src/main/res/values/styles.xml | 5 +- app/src/main/res/xml/shortcuts.xml | 2 +- .../com/nicholasdoglio/notes/Note.sq | 24 +- .../notes/data/repo/FakeQueries.kt | 70 ---- .../notes/data/repo/FakeRepository.kt | 56 --- .../notes/data/repo/NoteQueriesTest.kt | 160 ++++++++ .../notes/data/repo/NoteRepositoryTest.kt | 206 +++++----- .../editnote}/NoteViewModelTest.kt | 19 +- .../nicholasdoglio/notes/shared/TestData.kt | 12 +- .../{FakeSchedulers.kt => TestSchedulers.kt} | 5 +- build.gradle.kts | 15 +- buildSrc/build.gradle.kts | 25 +- buildSrc/src/main/kotlin/Config.kt | 15 +- buildSrc/src/main/kotlin/LibraryPlugin.kt | 33 ++ buildSrc/src/main/kotlin/Libs.kt | 376 +++++------------- buildSrc/src/main/kotlin/Plugins.kt | 38 +- buildSrc/src/main/kotlin/Versions.kt | 88 ++-- gradle.properties | 3 +- gradle/wrapper/gradle-wrapper.jar | Bin 58702 -> 58695 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 70 files changed, 1077 insertions(+), 1162 deletions(-) create mode 100644 app/sampledata/about.json create mode 100644 app/sampledata/notes.json rename ktlint.gradle.kts => app/src/main/kotlin/com/nicholasdoglio/notes/data/local/TimestampColumnAdapter.kt (64%) rename app/src/main/kotlin/com/nicholasdoglio/notes/di/{FragmentBinding.kt => BindingFragments.kt} (88%) rename app/src/main/kotlin/com/nicholasdoglio/notes/di/{ViewModelBinding.kt => BindingViewModels.kt} (90%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui => features}/InjectableNavHostFragment.kt (95%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui => features}/MainActivity.kt (93%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui => features}/about/AboutAdapter.kt (82%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui => features}/about/AboutFragment.kt (83%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui => features}/about/AboutItemViewHolder.kt (70%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui => features}/about/AboutViewModel.kt (88%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui => features}/about/LibsFragment.kt (95%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui/note => features/editnote}/DiscardFragment.kt (95%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui/note => features/editnote}/NoteAction.kt (92%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui/note => features/editnote}/NoteFragment.kt (97%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui/note => features/editnote}/NoteViewModel.kt (94%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui => features}/list/NoteListAdapter.kt (69%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui => features}/list/NoteListFragment.kt (69%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui => features}/list/NoteListViewModel.kt (94%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui => features}/list/NoteViewHolder.kt (69%) rename app/src/main/kotlin/com/nicholasdoglio/notes/{ui => features}/tile/CreateNoteTile.kt (92%) delete mode 100644 app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/FakeQueries.kt delete mode 100644 app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/FakeRepository.kt create mode 100644 app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/NoteQueriesTest.kt rename app/src/test/kotlin/com/nicholasdoglio/notes/{ui/note => features/editnote}/NoteViewModelTest.kt (73%) rename app/src/test/kotlin/com/nicholasdoglio/notes/shared/{FakeSchedulers.kt => TestSchedulers.kt} (90%) create mode 100644 buildSrc/src/main/kotlin/LibraryPlugin.kt diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index d85717b..528c930 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -18,7 +18,7 @@ jobs: - name: ktlint uses: eskatos/gradle-command-action@v1 with: - arguments: ktlint + arguments: ktlintCheck - name: detekt uses: eskatos/gradle-command-action@v1 with: diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9fedb4f..2a9ff6d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,10 +1,11 @@ +import dev.arunkumar.scabbard.gradle.ScabbardSpec import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent /* * MIT License * - * Copyright (c) 2019 Nicholas Doglio + * Copyright (c) 2020 Nicholas Doglio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,19 +27,16 @@ import org.gradle.api.tasks.testing.logging.TestLogEvent */ plugins { - id("com.android.application") - kotlin("android") - kotlin("android.extensions") - kotlin("kapt") - id("androidx.navigation.safeargs.kotlin") - id("io.gitlab.arturbosch.detekt") - id("com.squareup.sqldelight") -} - -apply(from = "$rootDir/ktlint.gradle.kts") - -androidExtensions { - isExperimental = true + id(Plugins.Android.application) + kotlin(Plugins.Kotlin.android) + kotlin(Plugins.Kotlin.kapt) + id(Plugins.Android.safeArgs) + id(Plugins.detekt) + id(Plugins.ktlint) + id(Plugins.sqlDelight) + id(Plugins.delect) + id(Plugins.license) + id(Plugins.scabbard) version Versions.scabbard } kapt { @@ -61,15 +59,30 @@ tasks.withType { } } +scabbard.configure(closureOf { + enabled(true) +}) + +delect { + daggerReflectVersion = Versions.daggerReflect +} + +ktlint { + version.set(Versions.ktlint) + android.set(true) + outputColorName.set("RED") + disabledRules.set(setOf("import-ordering")) +} + android { compileSdkVersion(Config.compileSdk) defaultConfig { - applicationId = "com.nicholasdoglio.notes" + applicationId = Config.applicationId minSdkVersion(Config.minSdk) targetSdkVersion(Config.targetSdk) versionCode = Config.versionCode versionName = Config.versionName - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunner = Config.testRunner } buildTypes { @@ -112,50 +125,53 @@ sqldelight { } dependencies { - implementation(Libs.kotlin_stdlib_jdk8) + implementation(Libs.Kotlin.Stdlib) - implementation(Libs.fragment_ktx) - implementation(Libs.appcompat) - implementation(Libs.recyclerview) - implementation(Libs.material) - implementation(Libs.constraintlayout) - implementation(Libs.navigation_fragment_ktx) - implementation(Libs.navigation_ui_ktx) + implementation(Libs.Android.fragmentKtx) + implementation(Libs.Android.appcompat) + implementation(Libs.Android.recyclerview) + implementation(Libs.Android.material) + implementation(Libs.Android.constraintLayout) + implementation(Libs.Android.navigationFragmentKtx) + implementation(Libs.Android.navigationUiKtx) + implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0") - implementation("com.squareup.sqldelight:android-driver:1.2.1") - implementation("com.squareup.sqldelight:rxjava2-extensions:1.2.1") + implementation(Libs.Square.sqlDelightAndroidDriver) + implementation(Libs.Square.sqlDelightRxExt) implementation(Libs.threetenabp) + implementation(Libs.Rx.rxjava) + implementation(Libs.Rx.rxkotlin) + implementation(Libs.Rx.rxandroid) + implementation(Libs.Rx.Binding) // TODO think about other bindings?) + implementation(Libs.Rx.BindingRecyclerView) + implementation(Libs.Rx.autodisposeAndroidArchcomponents) + implementation(Libs.Rx.rxdogtag) + implementation(Libs.Rx.rxdogtagAutodispose) + + implementation(Libs.Dagger.dagger) + kapt(Libs.Dagger.daggerCompiler) + + implementation(Libs.timber) + debugImplementation(Libs.Square.leakCanary) - implementation(Libs.rxjava) - implementation(Libs.rxkotlin) - implementation(Libs.rxandroid) - implementation(Libs.rxbinding) // TODO think about other bindings?) - implementation(Libs.rxbinding_recyclerview) - implementation(Libs.autodispose_android_archcomponents) - implementation(Libs.rxdogtag) - implementation(Libs.rxdogtag_autodispose) + testImplementation(Libs.Test.junit) + testImplementation(Libs.Test.truth) + testImplementation(Libs.Test.coreTesting) + testImplementation(Libs.Test.mockk) + testImplementation(Libs.Square.sqlDelightJvm) - implementation(Libs.dagger) - kapt(Libs.dagger_compiler) + testImplementation("org.threeten:threetenbp:1.4.0") { + exclude(group = "com.jakewharton.threetenabp", module = "threetenabp") + } - implementation(Libs.timber) - debugImplementation(Libs.leakcanary_android) - - testImplementation(Libs.junit_junit) - testImplementation(Libs.com_google_truth_truth) - testImplementation(Libs.core_testing) - testImplementation(Libs.mockk) - testImplementation("com.squareup.sqldelight:sqlite-driver:1.2.1") - - androidTestImplementation(Libs.fragment_testing) - androidTestImplementation(Libs.com_google_truth_truth) - androidTestImplementation(Libs.core_testing) - androidTestImplementation(Libs.room_testing) - androidTestImplementation(Libs.androidx_test_core) - androidTestImplementation(Libs.androidx_test_ext_junit) - androidTestImplementation(Libs.androidx_test_ext_truth) - androidTestImplementation(Libs.androidx_test_runner) - androidTestImplementation(Libs.androidx_test_rules) - androidTestImplementation(Libs.espresso_core) + androidTestImplementation(Libs.Android.fragmentTesting) + androidTestImplementation(Libs.Test.truth) + androidTestImplementation(Libs.Test.coreTesting) + androidTestImplementation(Libs.Test.androidxTestCore) + androidTestImplementation(Libs.Test.androidxTestExtJunit) + androidTestImplementation(Libs.Test.androidxTestExtTruth) + androidTestImplementation(Libs.Test.androidxTestRunner) + androidTestImplementation(Libs.Test.androidxTestRules) + androidTestImplementation(Libs.Test.espressoCore) } diff --git a/app/sampledata/about.json b/app/sampledata/about.json new file mode 100644 index 0000000..933aa3a --- /dev/null +++ b/app/sampledata/about.json @@ -0,0 +1,14 @@ +{ + "about": [ + { + "text": "Developed by Nicholas Doglio" + }, + { + "text": "Source Code" + }, + { + "text": "Libraries" + } + + ] +} diff --git a/app/sampledata/notes.json b/app/sampledata/notes.json new file mode 100644 index 0000000..8023ac8 --- /dev/null +++ b/app/sampledata/notes.json @@ -0,0 +1,69 @@ +{ + notes: [ + { + "id": 0, + "title": "Hello", + "contents": "World" + }, + { + "id": 1, + "title": "Hello", + "contents": "Friend" + }, + { + "id": 2, + "title": "To Do list", + "contents": "1. Finish this project. 2. Write lots of tests. 3. Write more tests" + }, + { + "id": 0, + "title": "Hello", + "contents": "World" + }, + { + "id": 0, + "title": "Hello", + "contents": "World" + }, + { + "id": 0, + "title": "Hello", + "contents": "World" + }, + { + "id": 0, + "title": "Hello", + "contents": "World" + }, + { + "id": 0, + "title": "Hello", + "contents": "World" + }, + { + "id": 0, + "title": "Hello", + "contents": "World" + }, + { + "id": 0, + "title": "Hello", + "contents": "World" + }, + { + "id": 0, + "title": "Hello", + "contents": "World" + }, + { + "id": 0, + "title": "Hello", + "contents": "World" + }, + { + "id": 0, + "title": "Hello", + "contents": "World" + } + ] +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 403057d..5f92b7b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + tools:context="com.nicholasdoglio.notes.features.about.AboutFragment"> + android:layout_height="match_parent" + android:padding="24dp" + tools:listitem="@layout/item_about" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_note.xml b/app/src/main/res/layout/fragment_note.xml index 5eda384..04b11dc 100644 --- a/app/src/main/res/layout/fragment_note.xml +++ b/app/src/main/res/layout/fragment_note.xml @@ -1,7 +1,7 @@ - - - - - - - - + tools:context="com.nicholasdoglio.notes.features.list.NoteListFragment"> + android:id="@+id/notes_recyclerview" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:listitem="@layout/item_note" /> + android:visibility="gone"> + android:textAppearance="@style/TextAppearance.AppCompat.Display1" /> + android:text="@string/empty_list_sub_text" /> + + + app:layout_anchor="@id/app_bar" /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_about.xml b/app/src/main/res/layout/item_about.xml index 0ca4186..4d3860f 100644 --- a/app/src/main/res/layout/item_about.xml +++ b/app/src/main/res/layout/item_about.xml @@ -1,7 +1,7 @@ @color/colorPrimary @color/colorPrimaryDark - @color/colorAccent + @color/colorAccent + @android:color/white \ No newline at end of file diff --git a/app/src/main/res/xml/shortcuts.xml b/app/src/main/res/xml/shortcuts.xml index 905eca7..c256e47 100644 --- a/app/src/main/res/xml/shortcuts.xml +++ b/app/src/main/res/xml/shortcuts.xml @@ -10,7 +10,7 @@ android:shortcutShortLabel="@string/create_note_shortcut"> diff --git a/app/src/main/sqldelight/com/nicholasdoglio/notes/Note.sq b/app/src/main/sqldelight/com/nicholasdoglio/notes/Note.sq index 84dd4a6..ee33c2f 100644 --- a/app/src/main/sqldelight/com/nicholasdoglio/notes/Note.sq +++ b/app/src/main/sqldelight/com/nicholasdoglio/notes/Note.sq @@ -1,11 +1,17 @@ +import org.threeten.bp.LocalDateTime; + + + CREATE TABLE Note ( id INTEGER NOT NULL UNIQUE PRIMARY KEY AUTOINCREMENT, title TEXT, - contents TEXT + contents TEXT, + timestamp TEXT AS LocalDateTime NOT NULL ); allNotes: -SELECT * FROM Note; +SELECT * FROM Note +ORDER BY timestamp; count: SELECT count(*) FROM Note; @@ -14,14 +20,16 @@ findNoteById: SELECT * FROM Note WHERE id = ?; insertOrReplace: -INSERT OR REPLACE INTO Note( - title, - contents -) -VALUES (?, ?); +INSERT OR REPLACE INTO Note(id, title, contents, timestamp) +VALUES (?, ?, ?, ?); deleteById: DELETE FROM Note -WHERE id = ?; \ No newline at end of file +WHERE id = ?; + +-- Just for testing +dropTable: +DELETE +FROM Note; \ No newline at end of file diff --git a/app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/FakeQueries.kt b/app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/FakeQueries.kt deleted file mode 100644 index a9d55c0..0000000 --- a/app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/FakeQueries.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2019 Nicholas Doglio - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.nicholasdoglio.notes.data.repo - -import com.nicholasdoglio.notes.Note -import com.nicholasdoglio.notes.NoteQueries -import com.squareup.sqldelight.Query -import com.squareup.sqldelight.Transacter - -class FakeQueries : NoteQueries { - - override fun allNotes( - mapper: (id: Long, title: String?, contents: String?) -> T - ): Query { - return TODO() - } - - override fun allNotes(): Query { - return TODO() - } - - override fun count(): Query { - return TODO() - } - - override fun findNoteById( - id: Long, - mapper: (id: Long, title: String?, contents: String?) -> T - ): Query { - return TODO() - } - - override fun findNoteById(id: Long): Query { - return TODO() - } - - override fun insertOrReplace(title: String?, contents: String?) { - return TODO() - } - - override fun deleteById(id: Long) { - return TODO() - } - - override fun transaction(noEnclosing: Boolean, body: Transacter.Transaction.() -> Unit) { - return TODO() - } -} diff --git a/app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/FakeRepository.kt b/app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/FakeRepository.kt deleted file mode 100644 index e8b0e31..0000000 --- a/app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/FakeRepository.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2019 Nicholas Doglio - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package com.nicholasdoglio.notes.data.repo - -import com.nicholasdoglio.notes.Note -import io.reactivex.BackpressureStrategy -import io.reactivex.Completable -import io.reactivex.Flowable -import io.reactivex.Maybe -import io.reactivex.subjects.BehaviorSubject - -class FakeRepository : Repository { - - private val notes: MutableMap = mutableMapOf() - private val _notes: BehaviorSubject> = - BehaviorSubject.createDefault(notes.map { it.value }.filterNotNull()) - private val _count: BehaviorSubject = BehaviorSubject.createDefault(0) - - override val observeCountOfItems: Flowable = - _count.hide().toFlowable(BackpressureStrategy.LATEST) - override val observeItems: Flowable> = - _notes.hide().map { it.toList() }.toFlowable(BackpressureStrategy.LATEST) - - override fun findItemById(id: Long): Maybe = - if (notes[id] == null) Maybe.never() else Maybe.just(notes[id]) - - override fun upsert(item: Note): Completable = Completable.fromAction { notes[item.id] = item } - - override fun delete(item: Note): Completable = Completable.fromAction { - notes.remove(item.id) - _count.onNext(notes.size.toLong()) - _notes.onNext(notes.map { it.value }.filterNotNull()) - } -} diff --git a/app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/NoteQueriesTest.kt b/app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/NoteQueriesTest.kt new file mode 100644 index 0000000..50f974b --- /dev/null +++ b/app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/NoteQueriesTest.kt @@ -0,0 +1,160 @@ +/* + * MIT License + * + * Copyright (c) 2020 Nicholas Doglio + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.nicholasdoglio.notes.data.repo + +import com.google.common.truth.Truth.assertThat +import com.nicholasdoglio.notes.Note +import com.nicholasdoglio.notes.NoteDatabase +import com.nicholasdoglio.notes.data.local.TimestampColumnAdapter +import com.nicholasdoglio.notes.shared.TestData +import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver +import org.junit.After +import org.junit.Test + +class NoteQueriesTest { + + private val inMemorySqlDriver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY).apply { + NoteDatabase.Schema.create(this) + } + + private val queries = + NoteDatabase(inMemorySqlDriver, Note.Adapter(TimestampColumnAdapter())).noteQueries + + @After + fun tearDown() { + queries.dropTable() + } + + @Test + fun `given DB is empty when all notes is called then return empty list`() { + assertThat(queries.allNotes().executeAsList()).isEmpty() + } + + @Test + fun `given three notes exist in DB when all notes is called then return list of three`() { + TestData.someNotes.forEach { + queries.insertOrReplace(it.id, it.title, it.contents, it.timestamp) + } + + assertThat(queries.allNotes().executeAsList()).isEqualTo(TestData.someNotes) + } + + @Test + fun `given DB is empty when count is called then return zero`() { + assertThat(queries.count().executeAsOne()) + .isEqualTo(0) + } + + @Test + fun `given three notes exist in DB when count is called then return three`() { + TestData.someNotes.forEach { + queries.insertOrReplace(it.id, it.title, it.contents, it.timestamp) + } + + assertThat(queries.count().executeAsOne()).isEqualTo(3) + } + + @Test + fun `given DB is empty when findNoteById is called then return null`() { + assertThat(queries.findNoteById(0).executeAsOneOrNull()) + .isNull() + } + + @Test + fun `given note exists when find by id is called then return note`() { + queries.insertOrReplace( + TestData.firstNote.id, + TestData.firstNote.title, + TestData.firstNote.contents, + TestData.firstNote.timestamp + ) + + assertThat(queries.findNoteById(TestData.firstNote.id).executeAsOneOrNull()) + .isEqualTo(TestData.firstNote) + } + + @Test + fun `given DB is empty when insertOrReplace is called then insert note`() { + queries.insertOrReplace( + TestData.firstNote.id, + TestData.firstNote.title, + TestData.firstNote.contents, + TestData.firstNote.timestamp + ) + + assertThat(queries.count().executeAsOne()) + .isEqualTo(1) + + assertThat(queries.allNotes().executeAsList()) + .isEqualTo(listOf(TestData.firstNote)) + + assertThat(queries.findNoteById(TestData.firstNote.id).executeAsOneOrNull()) + .isEqualTo(TestData.firstNote) + } + + @Test + fun `given note exists when insertOrReplace is called then update note`() { + queries.insertOrReplace( + TestData.firstNote.id, + TestData.firstNote.title, + TestData.firstNote.contents, + TestData.firstNote.timestamp + ) + + assertThat(queries.findNoteById(TestData.firstNote.id).executeAsOneOrNull()) + .isEqualTo(TestData.firstNote) + + val newNote = Note.Impl( + TestData.firstNote.id, + TestData.firstNote.title, + "Something something new", + TestData.firstNote.timestamp + ) + + queries.insertOrReplace(newNote.id, newNote.title, newNote.contents, newNote.timestamp) + + assertThat(queries.findNoteById(TestData.firstNote.id).executeAsOneOrNull()) + .isEqualTo(newNote) + } + + @Test + fun `give note exists when deleteById is called then delete note`() { + queries.insertOrReplace( + TestData.firstNote.id, + TestData.firstNote.title, + TestData.firstNote.contents, + TestData.firstNote.timestamp + ) + + queries.deleteById(TestData.firstNote.id) + + assertThat(queries.count().executeAsOne()) + .isEqualTo(0) + + assertThat(queries.allNotes().executeAsList()).isEmpty() + + assertThat(queries.findNoteById(TestData.firstNote.id).executeAsOneOrNull()).isEqualTo(null) + } +} diff --git a/app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/NoteRepositoryTest.kt b/app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/NoteRepositoryTest.kt index f39d538..4f0c440 100644 --- a/app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/NoteRepositoryTest.kt +++ b/app/src/test/kotlin/com/nicholasdoglio/notes/data/repo/NoteRepositoryTest.kt @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2019 Nicholas Doglio + * Copyright (c) 2020 Nicholas Doglio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,99 +24,119 @@ package com.nicholasdoglio.notes.data.repo -import com.nicholasdoglio.notes.shared.FakeSchedulers +import com.nicholasdoglio.notes.Note +import com.nicholasdoglio.notes.NoteDatabase +import com.nicholasdoglio.notes.data.local.TimestampColumnAdapter +import com.nicholasdoglio.notes.shared.TestSchedulers +import com.nicholasdoglio.notes.shared.TestData +import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver +import org.junit.Before +import org.junit.Test +import org.threeten.bp.LocalDateTime class NoteRepositoryTest { - private val repository = NoteRepository(FakeQueries(), FakeSchedulers()) - - // @Test - // fun `given repository is empty when observing number of notes then return zero`() { - // repository.observeCountOfItems - // .test() - // .assertValue(0) - // } - // - // @Test - // fun `given a note is inserted when observing the number of notes then return one`() { - // repository.upsert(TestData.firstNote) - // .andThen(repository.observeCountOfItems) - // .test() - // .assertValue(1) - // } - // - // @Test - // fun `given repository is empty when observing notes then return empty list`() { - // repository.observeItems - // .test() - // .assertValue(emptyList()) - // } - // - // @Test - // fun `given a note is inserted when observing notes then return a list of one note`() { - // repository.upsert(TestData.firstNote) - // .andThen(repository.observeItems) - // .test() - // .assertValue(listOf(TestData.firstNote)) - // } - // - // @Test - // fun `given note ID exists when findNoteById is called then return the correct note`() { - // TestData.someNotes.forEach { - // repository.upsert(it).test() - // } - // - // repository.findItemById(TestData.firstNote.id).test().assertValue(TestData.firstNote) - // repository.findItemById(TestData.secondNote.id).test().assertValue(TestData.secondNote) - // repository.findItemById(TestData.thirdNote.id).test().assertValue(TestData.thirdNote) - // } - // - // // given a note ID that doesn't exist in the repository - // // when finding a note then return null - // @Test - // fun `TODO Fix name`() { - // repository.findItemById(TestData.thirdNote.id).test() - // .assertNoValues() - // } - // - // @Test // TODO think about failure case? - // fun `given a note doesn't exist when upsert is triggered then insert it into DB`() { - // val note = Note.Impl(5, "Hello World", "Testing triggerUpsert success") - // repository.upsert(note) - // .andThen(repository.observeItems) - // .test() - // .assertValue(listOf(note)) - // - // repository.observeCountOfItems - // .test() - // .assertValue(1) - // - // repository.findItemById(note.id).test() - // .assertValue(note) - // } - // - // @Test // TODO think about failure case? - // fun `given a note exists in the DB when upsert is called then update the note in the DB`() { - // repository.upsert(TestData.firstNote).test() - // - // val newNote = Note.Impl(TestData.firstNote.id, "New Note", "New Note") - // - // repository.upsert(newNote).test() - // - // repository.findItemById(TestData.firstNote.id).test() - // .assertValue(newNote) - // } - // - // @Test // TODO think about failure case? - // fun `given a note exists when delete is triggered then remove the note from the DB`() { - // repository.upsert(TestData.firstNote) - // .andThen(repository.observeCountOfItems) - // .test() - // .assertValue(1) - // - // repository.delete(TestData.firstNote) - // .andThen(repository.observeCountOfItems) - // .test() - // .assertValue(0) - // } + private val inMemorySqlDriver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY).apply { + NoteDatabase.Schema.create(this) + } + + private val queries = + NoteDatabase(inMemorySqlDriver, Note.Adapter(TimestampColumnAdapter())).noteQueries + private lateinit var repository: Repository + + @Before + fun setUp() { + repository = NoteRepository(queries, TestSchedulers()) + } + + @Test + fun `given repository is empty when observing number of notes then return zero`() { + + repository.observeCountOfItems + .test() + .assertValue(0) + } + + @Test + fun `given a note is inserted when observing the number of notes then return one`() { + repository.upsert(TestData.firstNote) + .andThen(repository.observeCountOfItems) + .test() + .assertValue(1) + } + + @Test + fun `given repository is empty when observing notes then return empty list`() { + repository.observeItems + .test() + .assertValue(emptyList()) + } + + @Test + fun `given a note is inserted when observing notes then return a list of one note`() { + repository.upsert(TestData.firstNote) + .andThen(repository.observeItems) + .test() + .assertValue(listOf(TestData.firstNote)) + } + + @Test + fun `given note ID exists when findNoteById is called then return the correct note`() { + TestData.someNotes.forEach { + repository.upsert(it).test() + } + + repository.findItemById(TestData.firstNote.id).test().assertValue(TestData.firstNote) + repository.findItemById(TestData.secondNote.id).test().assertValue(TestData.secondNote) + repository.findItemById(TestData.thirdNote.id).test().assertValue(TestData.thirdNote) + } + + // given a note ID that doesn't exist in the repository + // when finding a note then return null + @Test + fun `TODO Fix name`() { + repository.findItemById(TestData.thirdNote.id).test() + .assertNoValues() + } + + @Test // TODO think about failure case? + fun `given a note doesn't exist when upsert is triggered then insert it into DB`() { + val note = + Note.Impl(10, "Hello World", "Testing triggerUpsert success", LocalDateTime.now()) + repository.upsert(note) + .andThen(repository.observeItems) + .test() + .assertValue(listOf(note)) + + repository.observeCountOfItems + .test() + .assertValue(1) + + repository.findItemById(note.id).test() + .assertValue(note) + } + + @Test // TODO think about failure case? + fun `given a note exists in the DB when upsert is called then update the note in the DB`() { + val newNote = Note.Impl(TestData.firstNote.id, "New Note", "New Note", LocalDateTime.now()) + + repository.upsert(TestData.firstNote) + .andThen(repository.upsert(newNote)) + .andThen(repository.findItemById(TestData.firstNote.id)) + .test() + .assertValue(newNote) + } + + @Test // TODO think about failure case? + fun `given a note exists when delete is triggered then remove the note from the DB`() { + repository.upsert(TestData.firstNote) + .andThen(repository.observeCountOfItems) + .test() + .assertValue(1) + + repository.delete(TestData.firstNote) + .andThen(repository.observeCountOfItems) + .test() + .assertValue(0) + } } diff --git a/app/src/test/kotlin/com/nicholasdoglio/notes/ui/note/NoteViewModelTest.kt b/app/src/test/kotlin/com/nicholasdoglio/notes/features/editnote/NoteViewModelTest.kt similarity index 73% rename from app/src/test/kotlin/com/nicholasdoglio/notes/ui/note/NoteViewModelTest.kt rename to app/src/test/kotlin/com/nicholasdoglio/notes/features/editnote/NoteViewModelTest.kt index 898ff37..522d558 100644 --- a/app/src/test/kotlin/com/nicholasdoglio/notes/ui/note/NoteViewModelTest.kt +++ b/app/src/test/kotlin/com/nicholasdoglio/notes/features/editnote/NoteViewModelTest.kt @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2019 Nicholas Doglio + * Copyright (c) 2020 Nicholas Doglio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,12 +22,16 @@ * SOFTWARE. */ -package com.nicholasdoglio.notes.ui.note +package com.nicholasdoglio.notes.features.editnote import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.nicholasdoglio.notes.Note -import com.nicholasdoglio.notes.data.repo.FakeRepository +import com.nicholasdoglio.notes.NoteDatabase +import com.nicholasdoglio.notes.data.local.TimestampColumnAdapter +import com.nicholasdoglio.notes.data.repo.NoteRepository import com.nicholasdoglio.notes.data.repo.Repository +import com.nicholasdoglio.notes.shared.TestSchedulers +import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver import org.junit.Before import org.junit.Rule import org.junit.Test @@ -37,7 +41,14 @@ class NoteViewModelTest { @get:Rule val instantRule = InstantTaskExecutorRule() - private val repository: Repository = FakeRepository() + private val inMemorySqlDriver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY).apply { + NoteDatabase.Schema.create(this) + } + + private val queries = + NoteDatabase(inMemorySqlDriver, Note.Adapter(TimestampColumnAdapter())).noteQueries + + private val repository: Repository = NoteRepository(queries, TestSchedulers()) private lateinit var viewModel: NoteViewModel @Before diff --git a/app/src/test/kotlin/com/nicholasdoglio/notes/shared/TestData.kt b/app/src/test/kotlin/com/nicholasdoglio/notes/shared/TestData.kt index 5506dc6..8e94bc1 100644 --- a/app/src/test/kotlin/com/nicholasdoglio/notes/shared/TestData.kt +++ b/app/src/test/kotlin/com/nicholasdoglio/notes/shared/TestData.kt @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2019 Nicholas Doglio + * Copyright (c) 2020 Nicholas Doglio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,15 +25,17 @@ package com.nicholasdoglio.notes.shared import com.nicholasdoglio.notes.Note +import org.threeten.bp.LocalDateTime // TODO should this be an object? object TestData { - val firstNote = Note.Impl(1, "First note", "This is my first note") - val secondNote = Note.Impl(2, "Second note", "This is my second note") - val thirdNote = Note.Impl(3, "Third note", "This is my third note") + val firstNote: Note = Note.Impl(1, "First note", "This is my first note", LocalDateTime.now()) + val secondNote: Note = + Note.Impl(2, "Second note", "This is my second note", LocalDateTime.now()) + val thirdNote: Note = Note.Impl(3, "Third note", "This is my third note", LocalDateTime.now()) - val someNotes = listOf( + val someNotes: List = listOf( firstNote, secondNote, thirdNote diff --git a/app/src/test/kotlin/com/nicholasdoglio/notes/shared/FakeSchedulers.kt b/app/src/test/kotlin/com/nicholasdoglio/notes/shared/TestSchedulers.kt similarity index 90% rename from app/src/test/kotlin/com/nicholasdoglio/notes/shared/FakeSchedulers.kt rename to app/src/test/kotlin/com/nicholasdoglio/notes/shared/TestSchedulers.kt index e446d06..b5d0e97 100644 --- a/app/src/test/kotlin/com/nicholasdoglio/notes/shared/FakeSchedulers.kt +++ b/app/src/test/kotlin/com/nicholasdoglio/notes/shared/TestSchedulers.kt @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2019 Nicholas Doglio + * Copyright (c) 2020 Nicholas Doglio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,8 +28,9 @@ import com.nicholasdoglio.notes.util.SchedulersProvider import io.reactivex.Scheduler import io.reactivex.schedulers.Schedulers -class FakeSchedulers : SchedulersProvider { +class TestSchedulers : SchedulersProvider { override val main: Scheduler = Schedulers.trampoline() override val background: Scheduler = Schedulers.trampoline() override val database: Scheduler = Schedulers.trampoline() + override val computation: Scheduler = Schedulers.trampoline() } diff --git a/build.gradle.kts b/build.gradle.kts index 67ed667..b2b4cd2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2019 Nicholas Doglio + * Copyright (c) 2020 Nicholas Doglio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,10 +28,13 @@ buildscript { jcenter() } dependencies { - classpath(Libs.com_android_tools_build_gradle) - classpath(Libs.kotlin_gradle_plugin) - classpath(Libs.navigation_safe_args_gradle_plugin) - classpath("com.squareup.sqldelight:gradle-plugin:1.2.1") + classpath(GradlePlugin.android) + classpath(GradlePlugin.kotlin) + classpath(GradlePlugin.navigationSafeArgs) + classpath(GradlePlugin.sqlDelight) + classpath(GradlePlugin.ktlint) + classpath(GradlePlugin.delect) + classpath(GradlePlugin.license) } } @@ -49,7 +52,7 @@ allprojects { tasks.wrapper { distributionType = Wrapper.DistributionType.ALL - gradleVersion = Versions.gradleLatestVersion + gradleVersion = Versions.gradle } tasks.register("clean") { diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 216fb73..1ee04f7 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2019 Nicholas Doglio + * Copyright (c) 2020 Nicholas Doglio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,10 +26,27 @@ plugins { `kotlin-dsl` } +kotlinDslPluginOptions { + experimentalWarning.set(false) +} + + repositories { + mavenCentral() + google() jcenter() } -kotlinDslPluginOptions { - experimentalWarning.set(false) -} +dependencies { + /* Example Dependency */ + /* Depend on the android gradle plugin, since we want to access it in our plugin */ + implementation("com.android.tools.build:gradle:3.5.3") + + /* Example Dependency */ + /* Depend on the kotlin plugin, since we want to access it in our plugin */ + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61") + + /* Depend on the default Gradle API's since we want to build a custom plugin */ + implementation(gradleApi()) + implementation(localGroovy()) +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index b92a432..d18c719 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2019 Nicholas Doglio + * Copyright (c) 2020 Nicholas Doglio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,4 +28,17 @@ object Config { const val minSdk = 21 const val versionCode = 5 const val versionName = "1.1.1" + const val applicationId = "com.nicholasdoglio.notes" + const val testRunner = "androidx.test.runner.AndroidJUnitRunner" } + + +// TODO maybe app versioning? +// object Versions { +// private const val versionMajor = 1 +// private const val versionMinor = 0 +// private const val buildNum = 0 +// +// const val versionCode: Int = ((versionMajor * 1000000) + (versionMinor * 1000) + buildNum) +// const val versionName: String = "$versionMajor.$versionMinor.$buildNum" +// } diff --git a/buildSrc/src/main/kotlin/LibraryPlugin.kt b/buildSrc/src/main/kotlin/LibraryPlugin.kt new file mode 100644 index 0000000..d66bc3a --- /dev/null +++ b/buildSrc/src/main/kotlin/LibraryPlugin.kt @@ -0,0 +1,33 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project + +/* + * MIT License + * + * Copyright (c) 2020 Nicholas Doglio + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +open class LibraryPlugin : Plugin { + override fun apply(target: Project) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index 50f23b2..7d7fac4 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -1,269 +1,113 @@ -/** - * Generated by https://github.com/jmfayard/buildSrcVersions - * - * Update this file with - * `$ ./gradlew buildSrcVersions` - */ object Libs { - /** - * https://github.com/JakeWharton/RxBinding/ - */ - const val rxbinding: String = "com.jakewharton.rxbinding3:rxbinding:" + - Versions.com_jakewharton_rxbinding3 - - /** - * https://github.com/JakeWharton/RxBinding/ - */ - const val rxbinding_recyclerview: String = - "com.jakewharton.rxbinding3:rxbinding-recyclerview:" + - Versions.com_jakewharton_rxbinding3 - - /** - * https://kotlinlang.org/ - */ - const val kotlin_android_extensions: String = - "org.jetbrains.kotlin:kotlin-android-extensions:" + - Versions.org_jetbrains_kotlin - - /** - * https://kotlinlang.org/ - */ - const val kotlin_android_extensions_runtime: String = - "org.jetbrains.kotlin:kotlin-android-extensions-runtime:" + Versions.org_jetbrains_kotlin - - /** - * https://kotlinlang.org/ - */ - const val kotlin_annotation_processing_gradle: String = - "org.jetbrains.kotlin:kotlin-annotation-processing-gradle:" + Versions.org_jetbrains_kotlin - - /** - * https://kotlinlang.org/ - */ - const val kotlin_gradle_plugin: String = "org.jetbrains.kotlin:kotlin-gradle-plugin:" + - Versions.org_jetbrains_kotlin - - /** - * https://kotlinlang.org/ - */ - const val kotlin_stdlib_jdk8: String = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:" + - Versions.org_jetbrains_kotlin - - /** - * https://developer.android.com/topic/libraries/architecture/index.html - */ - const val navigation_fragment_ktx: String = "androidx.navigation:navigation-fragment-ktx:" + - Versions.androidx_navigation - - /** - * https://developer.android.com/topic/libraries/architecture/index.html - */ - const val navigation_safe_args_gradle_plugin: String = - "androidx.navigation:navigation-safe-args-gradle-plugin:" + Versions.androidx_navigation - - /** - * https://developer.android.com/topic/libraries/architecture/index.html - */ - const val navigation_ui_ktx: String = "androidx.navigation:navigation-ui-ktx:" + - Versions.androidx_navigation - - /** - * https://developer.android.com/jetpack/androidx - */ - const val fragment_ktx: String = "androidx.fragment:fragment-ktx:" + Versions.androidx_fragment - - /** - * https://developer.android.com/jetpack/androidx - */ - const val fragment_testing: String = "androidx.fragment:fragment-testing:" + - Versions.androidx_fragment - - /** - * https://github.com/google/dagger - */ - const val dagger: String = "com.google.dagger:dagger:" + Versions.com_google_dagger - - /** - * https://github.com/google/dagger - */ - const val dagger_compiler: String = "com.google.dagger:dagger-compiler:" + - Versions.com_google_dagger - - /** - * https://github.com/uber/RxDogTag/ - */ - const val rxdogtag: String = "com.uber.rxdogtag:rxdogtag:" + Versions.com_uber_rxdogtag - - /** - * https://github.com/uber/RxDogTag/ - */ - const val rxdogtag_autodispose: String = "com.uber.rxdogtag:rxdogtag-autodispose:" + - Versions.com_uber_rxdogtag - - /** - * https://developer.android.com/topic/libraries/architecture/index.html - */ - const val room_compiler: String = "androidx.room:room-compiler:" + Versions.androidx_room - - /** - * https://developer.android.com/topic/libraries/architecture/index.html - */ - const val room_runtime: String = "androidx.room:room-runtime:" + Versions.androidx_room - - /** - * https://developer.android.com/topic/libraries/architecture/index.html - */ - const val room_rxjava2: String = "androidx.room:room-rxjava2:" + Versions.androidx_room - - /** - * https://developer.android.com/topic/libraries/architecture/index.html - */ - const val room_testing: String = "androidx.room:room-testing:" + Versions.androidx_room - - /** - * https://developer.android.com/testing - */ - const val androidx_test_core: String = "androidx.test:core:" + Versions.androidx_test - - /** - * https://developer.android.com/testing - */ - const val androidx_test_rules: String = "androidx.test:rules:" + Versions.androidx_test - - /** - * https://developer.android.com/testing - */ - const val androidx_test_runner: String = "androidx.test:runner:" + Versions.androidx_test - - /** - * https://developer.android.com/studio - */ - const val com_android_tools_build_gradle: String = "com.android.tools.build:gradle:" + - Versions.com_android_tools_build_gradle - - /** - * https://developer.android.com/testing - */ - const val androidx_test_ext_junit: String = "androidx.test.ext:junit:" + - Versions.androidx_test_ext_junit - - /** - * https://developer.android.com/testing - */ - const val androidx_test_ext_truth: String = "androidx.test.ext:truth:" + - Versions.androidx_test_ext_truth - - /** - * http://github.com/google/truth - */ - const val com_google_truth_truth: String = "com.google.truth:truth:" + - Versions.com_google_truth_truth - - /** - * http://junit.org - */ - const val junit_junit: String = "junit:junit:" + Versions.junit_junit - - const val io_gitlab_arturbosch_detekt_gradle_plugin: String = - "io.gitlab.arturbosch.detekt:io.gitlab.arturbosch.detekt.gradle.plugin:" + - Versions.io_gitlab_arturbosch_detekt_gradle_plugin - - const val de_fayard_buildsrcversions_gradle_plugin: String = - "de.fayard.buildSrcVersions:de.fayard.buildSrcVersions.gradle.plugin:" + - Versions.de_fayard_buildsrcversions_gradle_plugin - - /** - * https://github.com/uber/AutoDispose/ - */ - const val autodispose_android_archcomponents: String = - "com.uber.autodispose:autodispose-android-archcomponents:" + - Versions.autodispose_android_archcomponents - - const val dependency_analysis_gradle_plugin: String = - "gradle.plugin.com.autonomousapps:dependency-analysis-gradle-plugin:" + - Versions.dependency_analysis_gradle_plugin - - /** - * http://github.com/square/leakcanary/ - */ - const val leakcanary_android: String = "com.squareup.leakcanary:leakcanary-android:" + - Versions.leakcanary_android - - /** - * http://tools.android.com - */ - const val constraintlayout: String = "androidx.constraintlayout:constraintlayout:" + - Versions.constraintlayout - - /** - * https://developer.android.com/testing - */ - const val espresso_core: String = - "androidx.test.espresso:espresso-core:" + Versions.espresso_core - - /** - * https://developer.android.com/topic/libraries/architecture/index.html - */ - const val core_testing: String = "androidx.arch.core:core-testing:" + Versions.core_testing - - /** - * https://developer.android.com/jetpack/androidx - */ - const val recyclerview: String = "androidx.recyclerview:recyclerview:" + Versions.recyclerview - - /** - * https://developer.android.com/studio - */ - const val lint_gradle: String = "com.android.tools.lint:lint-gradle:" + Versions.lint_gradle - - /** - * https://github.com/JakeWharton/ThreeTenABP/ - */ + + object Square { + const val sqlDelightAndroidDriver = "com.squareup.sqldelight:android-driver:${Versions.sqlDelight}" + const val sqlDelightRxExt = "com.squareup.sqldelight:rxjava2-extensions:${Versions.sqlDelight}" + const val sqlDelightJvm = "com.squareup.sqldelight:sqlite-driver:${Versions.sqlDelight}" + const val leakCanary: String = "com.squareup.leakcanary:leakcanary-android:${Versions.leakCanary}" + } + + + object Rx { + const val Binding: String = "com.jakewharton.rxbinding3:rxbinding:${Versions.rxBinding}" + + const val BindingRecyclerView: String = + "com.jakewharton.rxbinding3:rxbinding-recyclerview:${Versions.rxBinding}" + + + const val rxdogtag: String = "com.uber.rxdogtag:rxdogtag:${Versions.dogTag}" + + const val rxdogtagAutodispose: String = + "com.uber.rxdogtag:rxdogtag-autodispose:${Versions.dogTag}" + + const val autodisposeAndroidArchcomponents: String = + "com.uber.autodispose:autodispose-android-archcomponents:${Versions.autoDispose}" + + + const val rxandroid: String = "io.reactivex.rxjava2:rxandroid:${Versions.rxandroid}" + + + const val rxkotlin: String = "io.reactivex.rxjava2:rxkotlin:${Versions.rxkotlin}" + + const val rxjava: String = "io.reactivex.rxjava2:rxjava:${Versions.rxjava}" + + } + + object Dagger { + + const val dagger: String = "com.google.dagger:dagger:${Versions.dagger}" + + const val daggerCompiler: String = "com.google.dagger:dagger-compiler:${Versions.dagger}" + + } + + object Kotlin { + + const val Stdlib: String = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlin}" + + } + + + object Android { + + const val navigationFragmentKtx: String = + "androidx.navigation:navigation-fragment-ktx:${Versions.navigation}" + + const val navigationUiKtx: String = + "androidx.navigation:navigation-ui-ktx:${Versions.navigation}" + + const val fragmentKtx: String = "androidx.fragment:fragment-ktx:${Versions.fragment}" + + const val fragmentTesting: String = + "androidx.fragment:fragment-testing:${Versions.fragment}" + + + const val material: String = "com.google.android.material:material:${Versions.material}" + + const val constraintLayout: String = + "androidx.constraintlayout:constraintlayout:${Versions.constraintLayout}" + + + const val recyclerview: String = + "androidx.recyclerview:recyclerview:${Versions.recyclerview}" + + const val appcompat: String = "androidx.appcompat:appcompat:${Versions.appcompat}" + + } + + object Test { + + + const val junit: String = "junit:junit:${Versions.junit}" + + const val androidxTestCore: String = "androidx.test:core:${Versions.androidTest}" + + const val androidxTestRules: String = "androidx.test:rules:${Versions.androidTest}" + + const val androidxTestRunner: String = "androidx.test:runner:${Versions.androidTest}" + + const val androidxTestExtJunit: String = + "androidx.test.ext:junit:${Versions.junitTextExtensions}" + + const val androidxTestExtTruth: String = + "androidx.test.ext:truth:${Versions.truthTestExtensions}" + + const val truth: String = "com.google.truth:truth:${Versions.truth}" + + const val espressoCore: String = + "androidx.test.espresso:espresso-core:${Versions.espressoCore}" + + const val coreTesting: String = "androidx.arch.core:core-testing:${Versions.coreTesting}" + + const val mockk: String = "io.mockk:mockk:${Versions.mockk}" + + } + + const val threetenabp: String = - "com.jakewharton.threetenabp:threetenabp:" + Versions.threetenabp - - /** - * https://developer.android.com/jetpack/androidx - */ - const val appcompat: String = "androidx.appcompat:appcompat:" + Versions.appcompat - - /** - * https://github.com/ReactiveX/RxAndroid - */ - const val rxandroid: String = "io.reactivex.rxjava2:rxandroid:" + Versions.rxandroid - - /** - * http://developer.android.com/tools/extras/support-library.html - */ - const val material: String = "com.google.android.material:material:" + Versions.material - - /** - * https://github.com/ReactiveX/RxKotlin - */ - const val rxkotlin: String = "io.reactivex.rxjava2:rxkotlin:" + Versions.rxkotlin - - /** - * https://github.com/pinterest/ktlint - */ - const val ktlint: String = "com.pinterest:ktlint:" + Versions.ktlint - - /** - * https://github.com/ReactiveX/RxJava - */ - const val rxjava: String = "io.reactivex.rxjava2:rxjava:" + Versions.rxjava - - /** - * https://github.com/JakeWharton/timber - */ - const val timber: String = "com.jakewharton.timber:timber:" + Versions.timber - - /** - * https://developer.android.com/studio - */ - const val aapt2: String = "com.android.tools.build:aapt2:" + Versions.aapt2 - - /** - * http://mockk.io - */ - const val mockk: String = "io.mockk:mockk:" + Versions.mockk + "com.jakewharton.threetenabp:threetenabp:${Versions.threetenabp}" + + + const val timber: String = "com.jakewharton.timber:timber:${Versions.timber}" + } diff --git a/buildSrc/src/main/kotlin/Plugins.kt b/buildSrc/src/main/kotlin/Plugins.kt index c69b451..9c84f52 100644 --- a/buildSrc/src/main/kotlin/Plugins.kt +++ b/buildSrc/src/main/kotlin/Plugins.kt @@ -4,7 +4,7 @@ import org.gradle.plugin.use.PluginDependencySpec /* * MIT License * - * Copyright (c) 2019 Nicholas Doglio + * Copyright (c) 2020 Nicholas Doglio * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,8 +27,40 @@ import org.gradle.plugin.use.PluginDependencySpec val PluginDependenciesSpec.detekt: PluginDependencySpec inline get() = - id("io.gitlab.arturbosch.detekt").version(Versions.io_gitlab_arturbosch_detekt_gradle_plugin) + id("io.gitlab.arturbosch.detekt").version(Versions.detekt) val PluginDependenciesSpec.benManesVersions: PluginDependencySpec inline get() = - id("com.github.ben-manes.versions").version("0.27.0") + id("com.github.ben-manes.versions").version(Versions.benManesVersions) + + + +object GradlePlugin { + const val sqlDelight = "com.squareup.sqldelight:gradle-plugin:${Versions.sqlDelight}" + const val ktlint = "org.jlleitschuh.gradle:ktlint-gradle:${Versions.ktlintGradlePlugin}" + const val navigationSafeArgs = "androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.navigation}" + const val kotlin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" + const val android = "com.android.tools.build:gradle:${Versions.androidBuildTools}" + const val delect = "com.soundcloud.delect:delect-plugin:${Versions.delect}" + const val license = "com.jaredsburrows:gradle-license-plugin:${Versions.licence}" +} + + +object Plugins { + object Android { + const val application = "com.android.application" + const val safeArgs = "androidx.navigation.safeargs.kotlin" + } + + object Kotlin { + const val android = "android" + const val kapt = "kapt" + } + + const val detekt = "io.gitlab.arturbosch.detekt" + const val ktlint = "org.jlleitschuh.gradle.ktlint" + const val sqlDelight = "com.squareup.sqldelight" + const val delect = "com.soundcloud.delect" + const val scabbard = "scabbard.gradle" + const val license = "com.jaredsburrows.license" +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 26b26bc..75c62f5 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -1,61 +1,56 @@ -import org.gradle.plugin.use.PluginDependenciesSpec -import org.gradle.plugin.use.PluginDependencySpec - -/** - * Generated by https://github.com/jmfayard/buildSrcVersions - * - * Find which updates are available by running - * `$ ./gradlew buildSrcVersions` - * This will only update the comments. - * - * YOU are responsible for updating manually the dependency version. - */ + + object Versions { - const val com_jakewharton_rxbinding3: String = "3.1.0" - const val org_jetbrains_kotlin: String = "1.3.61" + const val sqlDelight = "1.2.1" + + const val ktlintGradlePlugin = "9.1.1" + + const val benManesVersions = "0.27.0" - const val androidx_navigation: String = "2.2.0-rc03" + const val delect = "0.1.0" - const val androidx_fragment: String = "1.2.0-rc03" + const val daggerReflect: String = "0.2.0" - const val com_google_dagger: String = "2.25.2" + const val scabbard = "0.1.0" - const val com_uber_rxdogtag: String = "0.3.0" + const val rxBinding: String = "3.1.0" - const val androidx_room: String = "2.2.2" + const val kotlin: String = "1.3.61" - const val androidx_test: String = "1.3.0-alpha03" + const val navigation: String = "2.2.0-rc04" - const val com_android_tools_build_gradle: String = "3.5.3" + const val fragment: String = "1.2.0-rc05" - const val androidx_test_ext_junit: String = "1.1.2-alpha03" + const val dagger: String = "2.25.4" - const val androidx_test_ext_truth: String = "1.3.0-alpha03" + const val dogTag: String = "0.3.0" - const val com_google_truth_truth: String = "1.0" + const val androidTest: String = "1.3.0-alpha03" - const val junit_junit: String = "4.12" + const val androidBuildTools: String = "3.5.3" - const val io_gitlab_arturbosch_detekt_gradle_plugin: String = "1.2.2" + const val junitTextExtensions: String = "1.1.2-alpha03" - const val de_fayard_buildsrcversions_gradle_plugin: String = "0.7.0" + const val truthTestExtensions: String = "1.3.0-alpha03" - const val autodispose_android_archcomponents: String = "1.4.0" + const val truth: String = "1.0" - const val dependency_analysis_gradle_plugin: String = "0.4" + const val junit: String = "4.12" - const val leakcanary_android: String = "2.0" + const val detekt: String = "1.4.0" - const val constraintlayout: String = "2.0.0-beta3" + const val autoDispose: String = "1.4.0" - const val espresso_core: String = "3.3.0-alpha03" + const val leakCanary: String = "2.1" - const val core_testing: String = "2.1.0" + const val constraintLayout: String = "2.0.0-beta4" - const val recyclerview: String = "1.1.0" + const val espressoCore: String = "3.3.0-alpha03" - const val lint_gradle: String = "26.5.3" + const val coreTesting: String = "2.1.0" + + const val recyclerview: String = "1.2.0-alpha01" const val threetenabp: String = "1.2.1" @@ -63,32 +58,19 @@ object Versions { const val rxandroid: String = "2.1.1" - const val material: String = "1.2.0-alpha02" + const val material: String = "1.2.0-alpha03" const val rxkotlin: String = "2.4.0" const val ktlint: String = "0.36.0" - const val rxjava: String = "2.2.15" + const val rxjava: String = "2.2.17" const val timber: String = "4.7.1" - const val aapt2: String = "3.5.3-5435860" - const val mockk: String = "1.9.3" - /** - * Current version: "6.0.1" - * See issue 19: How to update Gradle itself? - * https://github.com/jmfayard/buildSrcVersions/issues/19 - */ - const val gradleLatestVersion: String = "6.0.1" -} + const val gradle: String = "6.1-rc-2" -/** - * See issue #47: how to update buildSrcVersions itself - * https://github.com/jmfayard/buildSrcVersions/issues/47 - */ -val PluginDependenciesSpec.buildSrcVersions: PluginDependencySpec - inline get() = - id("de.fayard.buildSrcVersions").version(Versions.de_fayard_buildsrcversions_gradle_plugin) + const val licence: String = "0.8.6" +} diff --git a/gradle.properties b/gradle.properties index 4fd043b..d3725a2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # # MIT License # -# Copyright (c) 2019 Nicholas Doglio +# Copyright (c) 2020 Nicholas Doglio # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -33,4 +33,3 @@ kapt.incremental.apt=true kapt.use.worker.api=true kapt.include.compile.classpath=false kotlin.parallel.tasks.in.project=true - diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index cc4fdc293d0e50b0ad9b65c16e7ddd1db2f6025b..f3d88b1c2faf2fc91d853cd5d4242b5547257070 100644 GIT binary patch delta 12333 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1ba7206..e3185a7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-rc-2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists