From c75f7ed025fdbd5a0ca42f52d377cb86bb53b405 Mon Sep 17 00:00:00 2001 From: Adetunji Dahunsi <dahunsi@google.com> Date: Tue, 1 Feb 2022 17:03:48 -0500 Subject: [PATCH] Add entity relationships and defined network deserialization strategy Change-Id: I239cdc28237a87a0ed6599892e8ac7c61776a46d --- app/build.gradle | 4 + .../ui/foryou/ForYouScreenTest.kt | 2 +- .../data/{news => }/fake/FakeData.kt | 649 +++--------------- .../{news => }/fake/FakeNewsRepository.kt | 31 +- .../nowinandroid/data/fake/FakeNiANetwork.kt | 46 ++ .../{news => }/fake/FakeTopicsRepository.kt | 17 +- .../nowinandroid/data/local/NiADatabase.kt | 49 ++ .../data/local/entities/AuthorEntity.kt | 40 ++ .../local/entities/EpisodeAuthorCrossRef.kt | 49 ++ .../data/local/entities/EpisodeEntity.kt | 41 ++ .../entities/NewsResourceAuthorCrossRef.kt | 49 ++ .../data/local/entities/NewsResourceEntity.kt | 51 ++ .../entities/NewsResourceTopicCrossRef.kt | 49 ++ .../data/local/entities/TopicEntity.kt | 38 + .../data/local/utilities/Converters.kt | 45 ++ .../apps/nowinandroid/data/model/Episode.kt | 44 ++ .../nowinandroid/data/model/NewsResource.kt | 52 ++ .../data/model/NewsResourceType.kt | 63 ++ .../data/{news => model}/Topic.kt | 10 +- .../data/network/NetworkAuthor.kt | 36 + .../data/network/NetworkEpisode.kt | 71 ++ .../data/network/NetworkNewsResource.kt | 76 ++ .../nowinandroid/data/network/NetworkTopic.kt | 36 + .../nowinandroid/data/network/NiANetwork.kt | 24 + .../utilities/InstantSerializer.kt} | 39 +- .../{news => repository}/NewsRepository.kt | 3 +- .../{news => repository}/TopicsRepository.kt | 5 +- .../samples/apps/nowinandroid/di/AppModule.kt | 15 +- .../apps/nowinandroid/ui/NewsResourceCard.kt | 4 +- .../nowinandroid/ui/foryou/ForYouScreen.kt | 4 +- .../nowinandroid/ui/foryou/ForYouViewModel.kt | 8 +- .../data/fake/FakeNewsRepositoryTest.kt | 35 + .../FakeNiANetworkTest.kt} | 13 +- .../data/network/NetworkEntityKtTest.kt | 129 ++++ .../testutil/TestNewsRepository.kt | 4 +- .../testutil/TestTopicsRepository.kt | 4 +- .../ui/foryou/ForYouViewModelTest.kt | 2 +- build.gradle | 2 +- gradle/libs.versions.toml | 5 + settings.gradle | 1 + 40 files changed, 1194 insertions(+), 651 deletions(-) rename app/src/main/java/com/google/samples/apps/nowinandroid/data/{news => }/fake/FakeData.kt (82%) rename app/src/main/java/com/google/samples/apps/nowinandroid/data/{news => }/fake/FakeNewsRepository.kt (58%) create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetwork.kt rename app/src/main/java/com/google/samples/apps/nowinandroid/data/{news => }/fake/FakeTopicsRepository.kt (73%) create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/local/NiADatabase.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/AuthorEntity.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/EpisodeAuthorCrossRef.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/EpisodeEntity.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/NewsResourceAuthorCrossRef.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/NewsResourceEntity.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/NewsResourceTopicCrossRef.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/TopicEntity.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/local/utilities/Converters.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/model/Episode.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/model/NewsResource.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/model/NewsResourceType.kt rename app/src/main/java/com/google/samples/apps/nowinandroid/data/{news => model}/Topic.kt (79%) create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkAuthor.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkEpisode.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkNewsResource.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkTopic.kt create mode 100644 app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NiANetwork.kt rename app/src/main/java/com/google/samples/apps/nowinandroid/data/{news/NewsResource.kt => network/utilities/InstantSerializer.kt} (58%) rename app/src/main/java/com/google/samples/apps/nowinandroid/data/{news => repository}/NewsRepository.kt (88%) rename app/src/main/java/com/google/samples/apps/nowinandroid/data/{news => repository}/TopicsRepository.kt (85%) create mode 100644 app/src/test/java/com/google/samples/apps/nowinandroid/data/fake/FakeNewsRepositoryTest.kt rename app/src/test/java/com/google/samples/apps/nowinandroid/data/{news/fake/FakeNewsRepositoryTest.kt => fake/FakeNiANetworkTest.kt} (80%) create mode 100644 app/src/test/java/com/google/samples/apps/nowinandroid/data/network/NetworkEntityKtTest.kt diff --git a/app/build.gradle b/app/build.gradle index 8966e4a0d..58c8c465a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,6 +21,7 @@ plugins { id 'jacoco' id 'dagger.hilt.android.plugin' alias(libs.plugins.protobuf) + alias(libs.plugins.ksp) } def jacocoTestReport = tasks.create("jacocoTestReport") @@ -161,6 +162,9 @@ dependencies { implementation libs.hilt.android kapt libs.hilt.compiler + implementation libs.room.runtime + ksp libs.room.compiler + implementation libs.protobuf.kotlin.lite debugImplementation libs.androidx.compose.ui.testManifest diff --git a/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouScreenTest.kt b/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouScreenTest.kt index 3f67709e6..adb0706e3 100644 --- a/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouScreenTest.kt +++ b/app/src/androidTest/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouScreenTest.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import com.google.samples.apps.nowinandroid.R -import com.google.samples.apps.nowinandroid.data.news.Topic +import com.google.samples.apps.nowinandroid.data.model.Topic import org.junit.Rule import org.junit.Test diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/fake/FakeData.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeData.kt similarity index 82% rename from app/src/main/java/com/google/samples/apps/nowinandroid/data/news/fake/FakeData.kt rename to app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeData.kt index 664283435..c6e97f921 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/fake/FakeData.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeData.kt @@ -14,17 +14,16 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.data.news.fake +package com.google.samples.apps.nowinandroid.data.fake -import com.google.samples.apps.nowinandroid.data.news.NewsResource -import com.google.samples.apps.nowinandroid.data.news.VideoInfo +import com.google.samples.apps.nowinandroid.data.network.NetworkNewsResource import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant import org.intellij.lang.annotations.Language object FakeDataSource { - val sampleResource = NewsResource( + val sampleResource = NetworkNewsResource( id = 1, episodeId = 52, title = "Thanks for helping us reach 1M YouTube Subscribers", @@ -42,11 +41,6 @@ object FakeDataSource { ).toInstant(TimeZone.UTC), type = "Video \uD83D\uDCFA", topics = listOf(0), - alternateVideo = VideoInfo( - url = "", - startTimestamp = 0, - endTimestamp = 0 - ) ) @Language("JSON") @@ -165,12 +159,7 @@ object FakeDataSource { "topics": [ 0 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 2, @@ -185,12 +174,7 @@ object FakeDataSource { ], "authors": [ 0 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 3, @@ -203,12 +187,7 @@ object FakeDataSource { "topics": [ 1 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 4, @@ -221,12 +200,7 @@ object FakeDataSource { "topics": [ 1 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 5, @@ -241,12 +215,7 @@ object FakeDataSource { ], "authors": [ 1 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 6, @@ -261,12 +230,7 @@ object FakeDataSource { ], "authors": [ 1 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 7, @@ -281,12 +245,7 @@ object FakeDataSource { ], "authors": [ 1 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 8, @@ -301,12 +260,7 @@ object FakeDataSource { ], "authors": [ 1 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 9, @@ -320,12 +274,7 @@ object FakeDataSource { 6, 1 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 10, @@ -339,12 +288,7 @@ object FakeDataSource { 6, 1 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 11, @@ -358,12 +302,7 @@ object FakeDataSource { 6, 14 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 12, @@ -377,12 +316,7 @@ object FakeDataSource { 6, 1 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 13, @@ -396,12 +330,7 @@ object FakeDataSource { 6, 18 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 14, @@ -417,12 +346,7 @@ object FakeDataSource { ], "authors": [ 2 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 15, @@ -438,12 +362,7 @@ object FakeDataSource { ], "authors": [ 3 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 16, @@ -459,12 +378,7 @@ object FakeDataSource { ], "authors": [ 4 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 17, @@ -477,12 +391,7 @@ object FakeDataSource { "topics": [ 3 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 18, @@ -497,12 +406,7 @@ object FakeDataSource { ], "authors": [ 5 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 19, @@ -517,12 +421,7 @@ object FakeDataSource { ], "authors": [ 5 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 20, @@ -538,12 +437,7 @@ object FakeDataSource { ], "authors": [ 6 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 21, @@ -560,12 +454,7 @@ object FakeDataSource { ], "authors": [ 7 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 22, @@ -578,12 +467,7 @@ object FakeDataSource { "topics": [ 10 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 23, @@ -596,12 +480,7 @@ object FakeDataSource { "topics": [ 10 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 24, @@ -616,12 +495,7 @@ object FakeDataSource { ], "authors": [ 8 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 25, @@ -636,12 +510,7 @@ object FakeDataSource { ], "authors": [ 9 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 26, @@ -655,12 +524,7 @@ object FakeDataSource { 10, 11 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 27, @@ -673,12 +537,7 @@ object FakeDataSource { "topics": [ 1 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 28, @@ -691,12 +550,7 @@ object FakeDataSource { "topics": [ 1 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 29, @@ -709,12 +563,7 @@ object FakeDataSource { "topics": [ 1 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 30, @@ -728,12 +577,7 @@ object FakeDataSource { 1, 4 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 31, @@ -746,12 +590,7 @@ object FakeDataSource { "topics": [ 1 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 32, @@ -765,12 +604,7 @@ object FakeDataSource { 1, 13 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 33, @@ -784,12 +618,7 @@ object FakeDataSource { 6, 1 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 34, @@ -802,12 +631,7 @@ object FakeDataSource { "topics": [ 13 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 35, @@ -820,12 +644,7 @@ object FakeDataSource { "topics": [ 13 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 36, @@ -838,12 +657,7 @@ object FakeDataSource { "topics": [ 1 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 37, @@ -858,12 +672,7 @@ object FakeDataSource { ], "authors": [ 10 - ], - "alternateVideo": { - "URL": "https://storage.googleapis.com/now-in-android/NIA049.mp4", - "startTimestamp": 82, - "endTimestamp": 97 - } + ] }, { "id": 38, @@ -876,12 +685,7 @@ object FakeDataSource { "topics": [ 14 ], - "authors": [], - "alternateVideo": { - "URL": "https://storage.googleapis.com/now-in-android/NIA049.mp4", - "startTimestamp": 97, - "endTimestamp": 121 - } + "authors": [] }, { "id": 39, @@ -894,12 +698,7 @@ object FakeDataSource { "topics": [ 1 ], - "authors": [], - "alternateVideo": { - "URL": "https://storage.googleapis.com/now-in-android/NIA049.mp4", - "startTimestamp": 193, - "endTimestamp": 206 - } + "authors": [] }, { "id": 40, @@ -913,12 +712,7 @@ object FakeDataSource { 12, 5 ], - "authors": [], - "alternateVideo": { - "URL": "https://storage.googleapis.com/now-in-android/NIA049.mp4", - "startTimestamp": 206, - "endTimestamp": 220 - } + "authors": [] }, { "id": 41, @@ -931,12 +725,7 @@ object FakeDataSource { "topics": [ 9 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 42, @@ -949,12 +738,7 @@ object FakeDataSource { "topics": [ 9 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 43, @@ -968,12 +752,7 @@ object FakeDataSource { 9, 1 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 44, @@ -987,12 +766,7 @@ object FakeDataSource { 9, 1 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 45, @@ -1005,12 +779,7 @@ object FakeDataSource { "topics": [ 9 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 46, @@ -1026,12 +795,7 @@ object FakeDataSource { ], "authors": [ 0 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 47, @@ -1047,12 +811,7 @@ object FakeDataSource { ], "authors": [ 11 - ], - "alternateVideo": { - "URL": "https://storage.googleapis.com/now-in-android/NIA049.mp4", - "startTimestamp": 49, - "endTimestamp": 82 - } + ] }, { "id": 48, @@ -1067,12 +826,7 @@ object FakeDataSource { ], "authors": [ 7 - ], - "alternateVideo": { - "URL": "https://storage.googleapis.com/now-in-android/NIA049.mp4", - "startTimestamp": 233, - "endTimestamp": 287 - } + ] }, { "id": 49, @@ -1087,12 +841,7 @@ object FakeDataSource { ], "authors": [ 5 - ], - "alternateVideo": { - "URL": "https://storage.googleapis.com/now-in-android/NIA049.mp4", - "startTimestamp": 136, - "endTimestamp": 193 - } + ] }, { "id": 50, @@ -1107,12 +856,7 @@ object FakeDataSource { ], "authors": [ 12 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 51, @@ -1128,12 +872,7 @@ object FakeDataSource { ], "authors": [ 6 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 52, @@ -1148,12 +887,7 @@ object FakeDataSource { ], "authors": [ 0 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 53, @@ -1168,12 +902,7 @@ object FakeDataSource { ], "authors": [ 13 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 54, @@ -1188,12 +917,7 @@ object FakeDataSource { ], "authors": [ 14 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 55, @@ -1209,12 +933,7 @@ object FakeDataSource { ], "authors": [ 6 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 56, @@ -1227,12 +946,7 @@ object FakeDataSource { "topics": [ 0 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 57, @@ -1247,12 +961,7 @@ object FakeDataSource { ], "authors": [ 15 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 58, @@ -1267,12 +976,7 @@ object FakeDataSource { ], "authors": [ 16 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 59, @@ -1287,12 +991,7 @@ object FakeDataSource { ], "authors": [ 17 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 60, @@ -1307,12 +1006,7 @@ object FakeDataSource { ], "authors": [ 18 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 61, @@ -1327,12 +1021,7 @@ object FakeDataSource { ], "authors": [ 19 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 62, @@ -1347,12 +1036,7 @@ object FakeDataSource { ], "authors": [ 20 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 63, @@ -1367,12 +1051,7 @@ object FakeDataSource { ], "authors": [ 21 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 64, @@ -1387,12 +1066,7 @@ object FakeDataSource { ], "authors": [ 5 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 65, @@ -1407,12 +1081,7 @@ object FakeDataSource { ], "authors": [ 22 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 66, @@ -1425,12 +1094,7 @@ object FakeDataSource { "topics": [ 15 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 67, @@ -1443,12 +1107,7 @@ object FakeDataSource { "topics": [ 6 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 68, @@ -1464,12 +1123,7 @@ object FakeDataSource { ], "authors": [ 23 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 69, @@ -1484,12 +1138,7 @@ object FakeDataSource { ], "authors": [ 7 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 70, @@ -1502,12 +1151,7 @@ object FakeDataSource { "topics": [ 7 ], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 71, @@ -1522,12 +1166,7 @@ object FakeDataSource { ], "authors": [ 24 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 72, @@ -1543,12 +1182,7 @@ object FakeDataSource { ], "authors": [ 25 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 73, @@ -1563,12 +1197,7 @@ object FakeDataSource { ], "authors": [ 11 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 74, @@ -1584,12 +1213,7 @@ object FakeDataSource { ], "authors": [ 26 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 75, @@ -1605,12 +1229,7 @@ object FakeDataSource { ], "authors": [ 27 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 76, @@ -1621,12 +1240,7 @@ object FakeDataSource { "publishDate": "2021-06-29T23:00:00.000Z", "type": "Jetpack release 🚀", "topics": [], - "authors": [], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + "authors": [] }, { "id": 77, @@ -1642,12 +1256,7 @@ object FakeDataSource { ], "authors": [ 28 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 78, @@ -1662,12 +1271,7 @@ object FakeDataSource { ], "authors": [ 15 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 79, @@ -1683,12 +1287,7 @@ object FakeDataSource { ], "authors": [ 29 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 80, @@ -1703,12 +1302,7 @@ object FakeDataSource { ], "authors": [ 10 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 81, @@ -1723,12 +1317,7 @@ object FakeDataSource { ], "authors": [ 10 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 82, @@ -1743,12 +1332,7 @@ object FakeDataSource { ], "authors": [ 30 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 83, @@ -1763,12 +1347,7 @@ object FakeDataSource { ], "authors": [ 7 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 84, @@ -1783,12 +1362,7 @@ object FakeDataSource { ], "authors": [ 31 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 85, @@ -1803,12 +1377,7 @@ object FakeDataSource { ], "authors": [ 32 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 86, @@ -1824,12 +1393,7 @@ object FakeDataSource { ], "authors": [ 33 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 87, @@ -1844,12 +1408,7 @@ object FakeDataSource { ], "authors": [ 8 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 88, @@ -1864,12 +1423,7 @@ object FakeDataSource { ], "authors": [ 34 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 89, @@ -1884,12 +1438,7 @@ object FakeDataSource { ], "authors": [ 7 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 90, @@ -1904,12 +1453,7 @@ object FakeDataSource { ], "authors": [ 23 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] }, { "id": 91, @@ -1924,12 +1468,7 @@ object FakeDataSource { ], "authors": [ 35 - ], - "alternateVideo": { - "URL": "", - "startTimestamp": 0, - "endTimestamp": 0 - } + ] } ] } diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/fake/FakeNewsRepository.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeNewsRepository.kt similarity index 58% rename from app/src/main/java/com/google/samples/apps/nowinandroid/data/news/fake/FakeNewsRepository.kt rename to app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeNewsRepository.kt index 883231e8a..12f116d85 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/fake/FakeNewsRepository.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeNewsRepository.kt @@ -14,18 +14,14 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.data.news.fake +package com.google.samples.apps.nowinandroid.data.fake -import com.google.samples.apps.nowinandroid.data.news.NewsRepository -import com.google.samples.apps.nowinandroid.data.news.NewsResource +import com.google.samples.apps.nowinandroid.data.model.NewsResource +import com.google.samples.apps.nowinandroid.data.repository.NewsRepository import com.google.samples.apps.nowinandroid.di.NiaDispatchers import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromString +import kotlinx.coroutines.flow.flowOf import kotlinx.serialization.json.Json /** @@ -36,21 +32,10 @@ class FakeNewsRepository @Inject constructor( private val dispatchers: NiaDispatchers, private val networkJson: Json ) : NewsRepository { - override fun getNewsResourcesStream(): Flow<List<NewsResource>> = flow { - emit(networkJson.decodeFromString<ResourceData>(FakeDataSource.data).resources) - } - .flowOn(dispatchers.IO) + + override fun getNewsResourcesStream(): Flow<List<NewsResource>> = + flowOf(emptyList()) override fun getNewsResourcesStream(filterTopicIds: Set<Int>): Flow<List<NewsResource>> = - getNewsResourcesStream().map { newsResources -> - newsResources.filter { it.topics.intersect(filterTopicIds.toSet()).isNotEmpty() } - } + flowOf(emptyList()) } - -/** - * Representation of resources aas fetched from [FakeDataSource] - */ -@Serializable -private data class ResourceData( - val resources: List<NewsResource> -) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetwork.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetwork.kt new file mode 100644 index 000000000..d9339fce0 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetwork.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.fake + +import com.google.samples.apps.nowinandroid.data.network.NetworkNewsResource +import com.google.samples.apps.nowinandroid.data.network.NiANetwork +import com.google.samples.apps.nowinandroid.di.NiaDispatchers +import kotlinx.coroutines.withContext +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json + +/** + * [NiANetwork] implementation that provides static news resources to aid development + */ +class FakeNiANetwork( + private val dispatchers: NiaDispatchers, + private val networkJson: Json +) : NiANetwork { + override suspend fun getNewsResources(): List<NetworkNewsResource> = + withContext(dispatchers.IO) { + networkJson.decodeFromString<ResourceData>(FakeDataSource.data).resources + } +} + +/** + * Representation of resources as fetched from [FakeDataSource] + */ +@Serializable +private data class ResourceData( + val resources: List<NetworkNewsResource> +) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/fake/FakeTopicsRepository.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeTopicsRepository.kt similarity index 73% rename from app/src/main/java/com/google/samples/apps/nowinandroid/data/news/fake/FakeTopicsRepository.kt rename to app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeTopicsRepository.kt index 81217e3fc..2a83d2cac 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/fake/FakeTopicsRepository.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeTopicsRepository.kt @@ -14,11 +14,12 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.data.news.fake +package com.google.samples.apps.nowinandroid.data.fake import com.google.samples.apps.nowinandroid.data.NiaPreferences -import com.google.samples.apps.nowinandroid.data.news.Topic -import com.google.samples.apps.nowinandroid.data.news.TopicsRepository +import com.google.samples.apps.nowinandroid.data.model.Topic +import com.google.samples.apps.nowinandroid.data.network.NetworkTopic +import com.google.samples.apps.nowinandroid.data.repository.TopicsRepository import com.google.samples.apps.nowinandroid.di.NiaDispatchers import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -33,7 +34,15 @@ class FakeTopicsRepository @Inject constructor( private val niaPreferences: NiaPreferences ) : TopicsRepository { override fun getTopicsStream(): Flow<List<Topic>> = flow<List<Topic>> { - emit(networkJson.decodeFromString(FakeDataSource.topicsData)) + emit( + networkJson.decodeFromString<List<NetworkTopic>>(FakeDataSource.topicsData).map { + Topic( + id = it.id, + name = it.name, + description = it.description + ) + } + ) } .flowOn(dispatchers.IO) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/NiADatabase.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/NiADatabase.kt new file mode 100644 index 000000000..e312b0567 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/NiADatabase.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.local + +import androidx.room.Database +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import com.google.samples.apps.nowinandroid.data.local.entities.AuthorEntity +import com.google.samples.apps.nowinandroid.data.local.entities.EpisodeAuthorCrossRef +import com.google.samples.apps.nowinandroid.data.local.entities.EpisodeEntity +import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceAuthorCrossRef +import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceEntity +import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceTopicCrossRef +import com.google.samples.apps.nowinandroid.data.local.entities.TopicEntity +import com.google.samples.apps.nowinandroid.data.local.utilities.InstantConverter +import com.google.samples.apps.nowinandroid.data.local.utilities.NewsResourceTypeConverter + +// TODO: ADD DAOs +@Database( + entities = [ + AuthorEntity::class, + EpisodeAuthorCrossRef::class, + EpisodeEntity::class, + NewsResourceAuthorCrossRef::class, + NewsResourceEntity::class, + NewsResourceTopicCrossRef::class, + TopicEntity::class, + ], + version = 1, +) +@TypeConverters( + InstantConverter::class, + NewsResourceTypeConverter::class, +) +abstract class NiADatabase : RoomDatabase() diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/AuthorEntity.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/AuthorEntity.kt new file mode 100644 index 000000000..735fb129a --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/AuthorEntity.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.local.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +/** + * Defines an author for either an [EpisodeEntity] or [NewsResourceEntity]. + * It has a many to many relationship with both entities + */ +@Entity( + tableName = "authors", + indices = [ + Index(value = ["name"], unique = true) + ], +) +data class AuthorEntity( + @PrimaryKey + val id: Int, + val name: String, + @ColumnInfo(name = "image_url") + val imageUrl: String, +) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/EpisodeAuthorCrossRef.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/EpisodeAuthorCrossRef.kt new file mode 100644 index 000000000..52212a393 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/EpisodeAuthorCrossRef.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.local.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey + +/** + * Cross reference for many to many relationship between [EpisodeEntity] and [AuthorEntity] + */ +@Entity( + tableName = "episodes_authors", + primaryKeys = ["episode_id", "author_id"], + foreignKeys = [ + ForeignKey( + entity = EpisodeEntity::class, + parentColumns = ["id"], + childColumns = ["episode_id"], + onDelete = ForeignKey.CASCADE + ), + ForeignKey( + entity = AuthorEntity::class, + parentColumns = ["id"], + childColumns = ["author_id"], + onDelete = ForeignKey.CASCADE + ), + ] +) +data class EpisodeAuthorCrossRef( + @ColumnInfo(name = "episode_id") + val episodeId: Int, + @ColumnInfo(name = "author_id") + val authorId: Long, +) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/EpisodeEntity.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/EpisodeEntity.kt new file mode 100644 index 000000000..bf6a7722f --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/EpisodeEntity.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.local.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import kotlinx.datetime.Instant + +/** + * Defines an NiA episode. + * It is a parent in a 1 to many relationship with [NewsResourceEntity] + */ +@Entity( + tableName = "episodes", +) +data class EpisodeEntity( + @PrimaryKey + val id: Int, + val name: String, + @ColumnInfo(name = "publish_date") + val publishDate: Instant, + @ColumnInfo(name = "alternate_video") + val alternateVideo: String?, + @ColumnInfo(name = "alternate_audio") + val alternateAudio: String?, +) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/NewsResourceAuthorCrossRef.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/NewsResourceAuthorCrossRef.kt new file mode 100644 index 000000000..10b17f8cf --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/NewsResourceAuthorCrossRef.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.local.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey + +/** + * Cross reference for many to many relationship between [NewsResourceEntity] and [AuthorEntity] + */ +@Entity( + tableName = "news_resources_authors", + primaryKeys = ["news_resource_id", "author_id"], + foreignKeys = [ + ForeignKey( + entity = NewsResourceEntity::class, + parentColumns = ["id"], + childColumns = ["news_resource_id"], + onDelete = ForeignKey.CASCADE + ), + ForeignKey( + entity = AuthorEntity::class, + parentColumns = ["id"], + childColumns = ["author_id"], + onDelete = ForeignKey.CASCADE + ), + ] +) +data class NewsResourceAuthorCrossRef( + @ColumnInfo(name = "news_resource_id") + val newsResourceId: Int, + @ColumnInfo(name = "author_id") + val authorId: Long, +) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/NewsResourceEntity.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/NewsResourceEntity.kt new file mode 100644 index 000000000..0c0bb938d --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/NewsResourceEntity.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.local.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.PrimaryKey +import kotlinx.datetime.Instant + +/** + * Defines an NiA news resource. + * It is the child in a 1 to many relationship with [EpisodeEntity] + */ +@Entity( + tableName = "news_resources", + foreignKeys = [ + ForeignKey( + entity = EpisodeEntity::class, + parentColumns = ["id"], + childColumns = ["episode_id"], + onDelete = ForeignKey.CASCADE + ), + ] +) +data class NewsResourceEntity( + @PrimaryKey + val id: Int, + @ColumnInfo(name = "episode_id") + val episodeId: Int, + val title: String, + val content: String, + val url: String, + @ColumnInfo(name = "publish_date") + val publishDate: Instant, + val type: String, +) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/NewsResourceTopicCrossRef.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/NewsResourceTopicCrossRef.kt new file mode 100644 index 000000000..3955a035e --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/NewsResourceTopicCrossRef.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.local.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.ForeignKey + +/** + * Cross reference for many to many relationship between [NewsResourceEntity] and [TopicEntity] + */ +@Entity( + tableName = "news_resources_topics", + primaryKeys = ["news_resource_id", "topic_id"], + foreignKeys = [ + ForeignKey( + entity = NewsResourceEntity::class, + parentColumns = ["id"], + childColumns = ["news_resource_id"], + onDelete = ForeignKey.CASCADE + ), + ForeignKey( + entity = TopicEntity::class, + parentColumns = ["id"], + childColumns = ["topic_id"], + onDelete = ForeignKey.CASCADE + ), + ] +) +data class NewsResourceTopicCrossRef( + @ColumnInfo(name = "news_resource_id") + val newsResourceId: Int, + @ColumnInfo(name = "topic_id") + val topicId: Int, +) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/TopicEntity.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/TopicEntity.kt new file mode 100644 index 000000000..d683ece36 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/entities/TopicEntity.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.local.entities + +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +/** + * Defines a topic a user may follow. + * It has a many to many relationship with [NewsResourceEntity] + */ +@Entity( + tableName = "topics", + indices = [ + Index(value = ["name"], unique = true) + ] +) +data class TopicEntity( + @PrimaryKey + val id: Int, + val name: String, + val description: String, +) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/utilities/Converters.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/utilities/Converters.kt new file mode 100644 index 000000000..25580530e --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/local/utilities/Converters.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.local.utilities + +import androidx.room.TypeConverter +import com.google.samples.apps.nowinandroid.data.model.NewsResourceType +import kotlinx.datetime.Instant + +class InstantConverter { + @TypeConverter + fun longToInstant(value: Long?): Instant? = + value?.let(Instant::fromEpochMilliseconds) + + @TypeConverter + fun instantToLong(instant: Instant?): Long? = + instant?.toEpochMilliseconds() +} + +class NewsResourceTypeConverter { + @TypeConverter + fun newsResourceTypeToString(value: NewsResourceType?): String? = + value?.let(NewsResourceType::name) + + @TypeConverter + fun stringToNewsResourceType(name: String?): NewsResourceType = when (name) { + null -> NewsResourceType.Unknown + else -> NewsResourceType.values() + .firstOrNull { type -> type.name == name } + ?: NewsResourceType.Unknown + } +} diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/Episode.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/Episode.kt new file mode 100644 index 000000000..964d072e6 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/Episode.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.model + +import androidx.room.Embedded +import androidx.room.Junction +import androidx.room.Relation +import com.google.samples.apps.nowinandroid.data.local.entities.AuthorEntity +import com.google.samples.apps.nowinandroid.data.local.entities.EpisodeAuthorCrossRef +import com.google.samples.apps.nowinandroid.data.local.entities.EpisodeEntity +import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceEntity + +/** + * External data layer representation of an NiA episode + */ +data class Episode( + @Embedded + val entity: EpisodeEntity, + @Relation( + parentColumn = "id", + entityColumn = "episode_id" + ) + val newsResources: List<NewsResourceEntity>, + @Relation( + parentColumn = "episode_id", + entityColumn = "author_id", + associateBy = Junction(EpisodeAuthorCrossRef::class) + ) + val authors: List<AuthorEntity> +) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/NewsResource.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/NewsResource.kt new file mode 100644 index 000000000..e6ff75498 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/NewsResource.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.model + +import androidx.room.Embedded +import androidx.room.Junction +import androidx.room.Relation +import com.google.samples.apps.nowinandroid.data.local.entities.AuthorEntity +import com.google.samples.apps.nowinandroid.data.local.entities.EpisodeEntity +import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceAuthorCrossRef +import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceEntity +import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceTopicCrossRef +import com.google.samples.apps.nowinandroid.data.local.entities.TopicEntity + +/** + * External data layer representation of a fully populated NiA news resource + */ +data class NewsResource( + @Embedded + val entity: NewsResourceEntity, + @Relation( + parentColumn = "episode_id", + entityColumn = "id" + ) + val episode: EpisodeEntity, + @Relation( + parentColumn = "news_resource_id", + entityColumn = "author_id", + associateBy = Junction(NewsResourceAuthorCrossRef::class) + ) + val authors: List<AuthorEntity>, + @Relation( + parentColumn = "news_resource_id", + entityColumn = "topic_id", + associateBy = Junction(NewsResourceTopicCrossRef::class) + ) + val topics: List<TopicEntity> +) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/NewsResourceType.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/NewsResourceType.kt new file mode 100644 index 000000000..4e10b2e52 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/NewsResourceType.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.model + +/** + * Type for [NewsResource] + */ +enum class NewsResourceType( + val displayText: String, + // TODO: descriptions should probably be string resources + val description: String +) { + Video( + displayText = "Video 📺", + description = "A video published on YouTube" + ), + APIChange( + displayText = "API change", + description = "An addition, deprecation or change to the Android platform APIs." + ), + Article( + displayText = "Article 📚", + description = "An article, typically on Medium or the official Android blog" + ), + Codelab( + displayText = "Codelab", + description = "A new or updated codelab" + ), + Podcast( + displayText = "Podcast 🎙", + description = "A podcast" + ), + Docs( + displayText = "Docs 📑", + description = "A new or updated piece of documentation" + ), + Event( + displayText = "Event 📆", + description = "Information about a developer event e.g. Android Developer Summit" + ), + DAC( + displayText = "DAC", + description = "Android version features - Information about features in an Android" + ), + Unknown( + displayText = "Unknown", + description = "Unknown" + ) +} diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/Topic.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/Topic.kt similarity index 79% rename from app/src/main/java/com/google/samples/apps/nowinandroid/data/news/Topic.kt rename to app/src/main/java/com/google/samples/apps/nowinandroid/data/model/Topic.kt index 45dc13967..0bd5f1f70 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/Topic.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/model/Topic.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Android Open Source Project + * Copyright 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.data.news +package com.google.samples.apps.nowinandroid.data.model -import kotlinx.serialization.Serializable - -@Serializable +/** + * External data layer representation of a NiA Topic + */ data class Topic( val id: Int, val name: String, diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkAuthor.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkAuthor.kt new file mode 100644 index 000000000..b66d97719 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkAuthor.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.network + +import com.google.samples.apps.nowinandroid.data.local.entities.AuthorEntity +import kotlinx.serialization.Serializable + +/** + * Network representation of [AuthorEntity] + */ +@Serializable +data class NetworkAuthor( + val id: Int, + val name: String, + val imageUrl: String, +) + +fun NetworkAuthor.asEntity() = AuthorEntity( + id = id, + name = name, + imageUrl = imageUrl +) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkEpisode.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkEpisode.kt new file mode 100644 index 000000000..924167a53 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkEpisode.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.network + +import androidx.room.PrimaryKey +import com.google.samples.apps.nowinandroid.data.local.entities.EpisodeEntity +import com.google.samples.apps.nowinandroid.data.network.utilities.InstantSerializer +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +/** + * Network representation of [EpisodeEntity] when fetched from /networkepisodes + */ +@Serializable +data class NetworkEpisode( + @PrimaryKey + val id: Int, + val name: String, + @Serializable(InstantSerializer::class) + val publishDate: Instant, + val alternateVideo: String?, + val alternateAudio: String?, + val newsResources: List<Int> = listOf(), + val authors: List<Int> = listOf(), +) + +/** + * Network representation of [EpisodeEntity] when fetched from /networkepisodes{id} + */ +@Serializable +data class NetworkEpisodeExpanded( + @PrimaryKey + val id: Int, + val name: String, + @Serializable(InstantSerializer::class) + val publishDate: Instant, + val alternateVideo: String, + val alternateAudio: String, + val newsResources: List<NetworkNewsResource> = listOf(), + val authors: List<NetworkAuthor> = listOf(), +) + +fun NetworkEpisode.asEntity() = EpisodeEntity( + id = id, + name = name, + publishDate = publishDate, + alternateVideo = alternateVideo, + alternateAudio = alternateAudio, +) + +fun NetworkEpisodeExpanded.asEntity() = EpisodeEntity( + id = id, + name = name, + publishDate = publishDate, + alternateVideo = alternateVideo, + alternateAudio = alternateAudio, +) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkNewsResource.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkNewsResource.kt new file mode 100644 index 000000000..89ed61cea --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkNewsResource.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.network + +import com.google.samples.apps.nowinandroid.data.local.entities.NewsResourceEntity +import com.google.samples.apps.nowinandroid.data.network.utilities.InstantSerializer +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +/** + * Network representation of [NewsResourceEntity] when fetched from /networkresources + */ +@Serializable +data class NetworkNewsResource( + val id: Int, + val episodeId: Int, + val title: String, + val content: String, + val url: String, + @Serializable(InstantSerializer::class) + val publishDate: Instant, + val type: String, + val authors: List<Int> = listOf(), + val topics: List<Int> = listOf(), +) + +/** + * Network representation of [NewsResourceEntity] when fetched from /networkresources{id} + */ +@Serializable +data class NetworkNewsResourceExpanded( + val id: Int, + val episodeId: Int, + val title: String, + val content: String, + val url: String, + @Serializable(InstantSerializer::class) + val publishDate: Instant, + val type: String, + val authors: List<NetworkAuthor> = listOf(), + val topics: List<NetworkTopic> = listOf(), +) + +fun NetworkNewsResource.asEntity() = NewsResourceEntity( + id = id, + episodeId = episodeId, + title = title, + content = content, + url = url, + publishDate = publishDate, + type = type, +) + +fun NetworkNewsResourceExpanded.asEntity() = NewsResourceEntity( + id = id, + episodeId = episodeId, + title = title, + content = content, + url = url, + publishDate = publishDate, + type = type, +) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkTopic.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkTopic.kt new file mode 100644 index 000000000..208720b57 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NetworkTopic.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.network + +import com.google.samples.apps.nowinandroid.data.local.entities.TopicEntity +import kotlinx.serialization.Serializable + +/** + * Network representation of [TopicEntity] + */ +@Serializable +data class NetworkTopic( + val id: Int, + val name: String = "", + val description: String = "", +) + +fun NetworkTopic.asEntity() = TopicEntity( + id = id, + name = name, + description = description +) diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NiANetwork.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NiANetwork.kt new file mode 100644 index 000000000..2e2903051 --- /dev/null +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/NiANetwork.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.network + +/** + * Interface representing network calls to the NIA backend + */ +interface NiANetwork { + suspend fun getNewsResources(): List<NetworkNewsResource> +} diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/NewsResource.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/utilities/InstantSerializer.kt similarity index 58% rename from app/src/main/java/com/google/samples/apps/nowinandroid/data/news/NewsResource.kt rename to app/src/main/java/com/google/samples/apps/nowinandroid/data/network/utilities/InstantSerializer.kt index e3657abc2..fa8d17373 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/NewsResource.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/network/utilities/InstantSerializer.kt @@ -14,55 +14,24 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.data.news +package com.google.samples.apps.nowinandroid.data.network.utilities import kotlinx.datetime.Instant import kotlinx.datetime.toInstant import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveKind.STRING import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -/** - * Item representing a summary of a noteworthy item from a Now In Android episode - */ -@Serializable -data class NewsResource( - val id: Int, - val episodeId: Int, - val title: String, - val content: String, - val url: String, - val authors: List<Int>, - @Serializable(InstantSerializer::class) - val publishDate: Instant, - val type: String, - val topics: List<Int>, - val alternateVideo: VideoInfo? -) - -/** - * Data class summarizing video metadata - */ -@Serializable -data class VideoInfo( - @SerialName("URL") - val url: String, - val startTimestamp: Int, - val endTimestamp: Int, -) - -private object InstantSerializer : KSerializer<Instant> { +object InstantSerializer : KSerializer<Instant> { override fun deserialize(decoder: Decoder): Instant = decoder.decodeString().toInstant() override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor( serialName = "Instant", - kind = PrimitiveKind.STRING + kind = STRING ) override fun serialize(encoder: Encoder, value: Instant) = diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/NewsRepository.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/repository/NewsRepository.kt similarity index 88% rename from app/src/main/java/com/google/samples/apps/nowinandroid/data/news/NewsRepository.kt rename to app/src/main/java/com/google/samples/apps/nowinandroid/data/repository/NewsRepository.kt index 44e8db2fc..973604b1e 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/NewsRepository.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/repository/NewsRepository.kt @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.data.news +package com.google.samples.apps.nowinandroid.data.repository +import com.google.samples.apps.nowinandroid.data.model.NewsResource import kotlinx.coroutines.flow.Flow /** diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/TopicsRepository.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/data/repository/TopicsRepository.kt similarity index 85% rename from app/src/main/java/com/google/samples/apps/nowinandroid/data/news/TopicsRepository.kt rename to app/src/main/java/com/google/samples/apps/nowinandroid/data/repository/TopicsRepository.kt index 687a93df2..80d1ded81 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/TopicsRepository.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/data/repository/TopicsRepository.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Android Open Source Project + * Copyright 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.data.news +package com.google.samples.apps.nowinandroid.data.repository +import com.google.samples.apps.nowinandroid.data.model.Topic import kotlinx.coroutines.flow.Flow interface TopicsRepository { diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/di/AppModule.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/di/AppModule.kt index 406d2bb19..2f5a4c997 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/di/AppModule.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/di/AppModule.kt @@ -22,10 +22,12 @@ import androidx.datastore.core.DataStoreFactory import androidx.datastore.dataStoreFile import com.google.samples.apps.nowinandroid.data.UserPreferences import com.google.samples.apps.nowinandroid.data.UserPreferencesSerializer -import com.google.samples.apps.nowinandroid.data.news.NewsRepository -import com.google.samples.apps.nowinandroid.data.news.TopicsRepository -import com.google.samples.apps.nowinandroid.data.news.fake.FakeNewsRepository -import com.google.samples.apps.nowinandroid.data.news.fake.FakeTopicsRepository +import com.google.samples.apps.nowinandroid.data.fake.FakeNewsRepository +import com.google.samples.apps.nowinandroid.data.fake.FakeNiANetwork +import com.google.samples.apps.nowinandroid.data.fake.FakeTopicsRepository +import com.google.samples.apps.nowinandroid.data.network.NiANetwork +import com.google.samples.apps.nowinandroid.data.repository.NewsRepository +import com.google.samples.apps.nowinandroid.data.repository.TopicsRepository import dagger.Binds import dagger.Module import dagger.Provides @@ -39,6 +41,11 @@ import kotlinx.serialization.json.Json @InstallIn(SingletonComponent::class) interface AppModule { + @Binds + fun bindsNiANetwork( + fakeNiANetwork: FakeNiANetwork + ): NiANetwork + @Binds fun bindsTopicRepository(fakeTopicsRepository: FakeTopicsRepository): TopicsRepository diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NewsResourceCard.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NewsResourceCard.kt index 96001c9d4..2ed94d520 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NewsResourceCard.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/NewsResourceCard.kt @@ -30,11 +30,11 @@ import androidx.compose.ui.semantics.onClick import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.Preview import com.google.samples.apps.nowinandroid.R -import com.google.samples.apps.nowinandroid.data.news.NewsResource +import com.google.samples.apps.nowinandroid.data.model.NewsResource import com.google.samples.apps.nowinandroid.ui.theme.NiaTheme /** - * [com.google.samples.apps.nowinandroid.data.news.NewsResource] card used on the following screens: + * [com.google.samples.apps.nowinandroid.data.model.NewsResource] card used on the following screens: * For You, Episodes, Saved */ diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouScreen.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouScreen.kt index 62782f8f8..0034517da 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouScreen.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouScreen.kt @@ -44,8 +44,8 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.google.accompanist.flowlayout.FlowRow import com.google.samples.apps.nowinandroid.R -import com.google.samples.apps.nowinandroid.data.news.NewsResource -import com.google.samples.apps.nowinandroid.data.news.Topic +import com.google.samples.apps.nowinandroid.data.model.NewsResource +import com.google.samples.apps.nowinandroid.data.model.Topic @Composable fun ForYouRoute( diff --git a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouViewModel.kt b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouViewModel.kt index d48362f7a..49e88c47c 100644 --- a/app/src/main/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouViewModel.kt +++ b/app/src/main/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouViewModel.kt @@ -22,10 +22,10 @@ import androidx.compose.runtime.snapshots.Snapshot.Companion.withMutableSnapshot import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.google.samples.apps.nowinandroid.data.news.NewsRepository -import com.google.samples.apps.nowinandroid.data.news.NewsResource -import com.google.samples.apps.nowinandroid.data.news.Topic -import com.google.samples.apps.nowinandroid.data.news.TopicsRepository +import com.google.samples.apps.nowinandroid.data.model.NewsResource +import com.google.samples.apps.nowinandroid.data.model.Topic +import com.google.samples.apps.nowinandroid.data.repository.NewsRepository +import com.google.samples.apps.nowinandroid.data.repository.TopicsRepository import com.google.samples.apps.nowinandroid.ui.saveable import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject diff --git a/app/src/test/java/com/google/samples/apps/nowinandroid/data/fake/FakeNewsRepositoryTest.kt b/app/src/test/java/com/google/samples/apps/nowinandroid/data/fake/FakeNewsRepositoryTest.kt new file mode 100644 index 000000000..7f0cc42cf --- /dev/null +++ b/app/src/test/java/com/google/samples/apps/nowinandroid/data/fake/FakeNewsRepositoryTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.fake + +import com.google.samples.apps.nowinandroid.di.DefaultNiaDispatchers +import kotlinx.serialization.json.Json +import org.junit.Before + +class FakeNewsRepositoryTest { + + private lateinit var subject: FakeNewsRepository + + @Before + fun setup() { + subject = FakeNewsRepository( + // TODO: Create test-specific NiaDispatchers + dispatchers = DefaultNiaDispatchers(), + networkJson = Json { ignoreUnknownKeys = true } + ) + } +} diff --git a/app/src/test/java/com/google/samples/apps/nowinandroid/data/news/fake/FakeNewsRepositoryTest.kt b/app/src/test/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetworkTest.kt similarity index 80% rename from app/src/test/java/com/google/samples/apps/nowinandroid/data/news/fake/FakeNewsRepositoryTest.kt rename to app/src/test/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetworkTest.kt index e1ca56857..bc66f226c 100644 --- a/app/src/test/java/com/google/samples/apps/nowinandroid/data/news/fake/FakeNewsRepositoryTest.kt +++ b/app/src/test/java/com/google/samples/apps/nowinandroid/data/fake/FakeNiANetworkTest.kt @@ -14,23 +14,22 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.data.news.fake +package com.google.samples.apps.nowinandroid.data.fake import com.google.samples.apps.nowinandroid.di.DefaultNiaDispatchers -import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import kotlinx.serialization.json.Json import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test -class FakeNewsRepositoryTest { +class FakeNiANetworkTest { - private lateinit var subject: FakeNewsRepository + private lateinit var subject: FakeNiANetwork @Before - fun setup() { - subject = FakeNewsRepository( + fun setUp() { + subject = FakeNiANetwork( // TODO: Create test-specific NiaDispatchers dispatchers = DefaultNiaDispatchers(), networkJson = Json { ignoreUnknownKeys = true } @@ -41,7 +40,7 @@ class FakeNewsRepositoryTest { fun testDeserializationOfNewsResources() = runTest { assertEquals( FakeDataSource.sampleResource, - subject.getNewsResourcesStream().first().first() + subject.getNewsResources().first() ) } } diff --git a/app/src/test/java/com/google/samples/apps/nowinandroid/data/network/NetworkEntityKtTest.kt b/app/src/test/java/com/google/samples/apps/nowinandroid/data/network/NetworkEntityKtTest.kt new file mode 100644 index 000000000..81e8a2ce9 --- /dev/null +++ b/app/src/test/java/com/google/samples/apps/nowinandroid/data/network/NetworkEntityKtTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.samples.apps.nowinandroid.data.network + +import com.google.samples.apps.nowinandroid.data.model.NewsResourceType +import kotlinx.datetime.Instant +import org.junit.Assert.assertEquals +import org.junit.Test + +class NetworkEntityKtTest { + + @Test + fun network_author_can_be_mapped_to_author_entity() { + val networkModel = NetworkAuthor( + id = 0, + name = "Test", + imageUrl = "something" + ) + val entity = networkModel.asEntity() + + assertEquals(0, entity.id) + assertEquals("Test", entity.name) + assertEquals("something", entity.imageUrl) + } + + @Test + fun network_topic_can_be_mapped_to_topic_entity() { + val networkModel = NetworkTopic( + id = 0, + name = "Test", + description = "something" + ) + val entity = networkModel.asEntity() + + assertEquals(0, entity.id) + assertEquals("Test", entity.name) + assertEquals("something", entity.description) + } + + @Test + fun network_news_resource_can_be_mapped_to_news_resource_entity() { + val networkModel = NetworkNewsResource( + id = 0, + episodeId = 2, + title = "title", + content = "content", + url = "url", + publishDate = Instant.fromEpochMilliseconds(1), + type = NewsResourceType.Article.displayText, + ) + val entity = networkModel.asEntity() + + assertEquals(0, entity.id) + assertEquals(2, entity.episodeId) + assertEquals("title", entity.title) + assertEquals("content", entity.content) + assertEquals("url", entity.url) + assertEquals(Instant.fromEpochMilliseconds(1), entity.publishDate) + assertEquals(NewsResourceType.Article.displayText, entity.type) + + val expandedNetworkModel = NetworkNewsResourceExpanded( + id = 0, + episodeId = 2, + title = "title", + content = "content", + url = "url", + publishDate = Instant.fromEpochMilliseconds(1), + type = NewsResourceType.Article.displayText, + ) + + val entityFromExpanded = expandedNetworkModel.asEntity() + + assertEquals(0, entityFromExpanded.id) + assertEquals(2, entityFromExpanded.episodeId) + assertEquals("title", entityFromExpanded.title) + assertEquals("content", entityFromExpanded.content) + assertEquals("url", entityFromExpanded.url) + assertEquals(Instant.fromEpochMilliseconds(1), entityFromExpanded.publishDate) + assertEquals(NewsResourceType.Article.displayText, entityFromExpanded.type) + } + + @Test + fun network_episode_can_be_mapped_to_episode_entity() { + val networkModel = NetworkEpisode( + id = 0, + name = "name", + publishDate = Instant.fromEpochMilliseconds(1), + alternateVideo = "alternateVideo", + alternateAudio = "alternateAudio", + ) + val entity = networkModel.asEntity() + + assertEquals(0, entity.id) + assertEquals("name", entity.name) + assertEquals("alternateVideo", entity.alternateVideo) + assertEquals("alternateAudio", entity.alternateAudio) + assertEquals(Instant.fromEpochMilliseconds(1), entity.publishDate) + + val expandedNetworkModel = NetworkEpisodeExpanded( + id = 0, + name = "name", + publishDate = Instant.fromEpochMilliseconds(1), + alternateVideo = "alternateVideo", + alternateAudio = "alternateAudio", + ) + + val entityFromExpanded = expandedNetworkModel.asEntity() + + assertEquals(0, entityFromExpanded.id) + assertEquals("name", entityFromExpanded.name) + assertEquals("alternateVideo", entityFromExpanded.alternateVideo) + assertEquals("alternateAudio", entityFromExpanded.alternateAudio) + assertEquals(Instant.fromEpochMilliseconds(1), entityFromExpanded.publishDate) + } +} diff --git a/app/src/test/java/com/google/samples/apps/nowinandroid/testutil/TestNewsRepository.kt b/app/src/test/java/com/google/samples/apps/nowinandroid/testutil/TestNewsRepository.kt index e1908ecce..c47db0d91 100644 --- a/app/src/test/java/com/google/samples/apps/nowinandroid/testutil/TestNewsRepository.kt +++ b/app/src/test/java/com/google/samples/apps/nowinandroid/testutil/TestNewsRepository.kt @@ -16,8 +16,8 @@ package com.google.samples.apps.nowinandroid.testutil -import com.google.samples.apps.nowinandroid.data.news.NewsRepository -import com.google.samples.apps.nowinandroid.data.news.NewsResource +import com.google.samples.apps.nowinandroid.data.model.NewsResource +import com.google.samples.apps.nowinandroid.data.repository.NewsRepository import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow diff --git a/app/src/test/java/com/google/samples/apps/nowinandroid/testutil/TestTopicsRepository.kt b/app/src/test/java/com/google/samples/apps/nowinandroid/testutil/TestTopicsRepository.kt index 4150fe405..bb60d885f 100644 --- a/app/src/test/java/com/google/samples/apps/nowinandroid/testutil/TestTopicsRepository.kt +++ b/app/src/test/java/com/google/samples/apps/nowinandroid/testutil/TestTopicsRepository.kt @@ -16,8 +16,8 @@ package com.google.samples.apps.nowinandroid.testutil -import com.google.samples.apps.nowinandroid.data.news.Topic -import com.google.samples.apps.nowinandroid.data.news.TopicsRepository +import com.google.samples.apps.nowinandroid.data.model.Topic +import com.google.samples.apps.nowinandroid.data.repository.TopicsRepository import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow diff --git a/app/src/test/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouViewModelTest.kt b/app/src/test/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouViewModelTest.kt index 2dc80c5b1..2e56e1d58 100644 --- a/app/src/test/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouViewModelTest.kt +++ b/app/src/test/java/com/google/samples/apps/nowinandroid/ui/foryou/ForYouViewModelTest.kt @@ -18,7 +18,7 @@ package com.google.samples.apps.nowinandroid.ui.foryou import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test -import com.google.samples.apps.nowinandroid.data.news.Topic +import com.google.samples.apps.nowinandroid.data.model.Topic import com.google.samples.apps.nowinandroid.testutil.TestDispatcherRule import com.google.samples.apps.nowinandroid.testutil.TestNewsRepository import com.google.samples.apps.nowinandroid.testutil.TestTopicsRepository diff --git a/build.gradle b/build.gradle index b2d8f4fa8..fcc2bdb2c 100644 --- a/build.gradle +++ b/build.gradle @@ -49,7 +49,7 @@ subprojects { target '**/*.kt' targetExclude("$buildDir/**/*.kt") targetExclude('bin/**/*.kt') - targetExclude("$rootDir/app/src/main/java/com/google/samples/apps/nowinandroid/data/news/fake/FakeData.kt") + targetExclude("$rootDir/app/src/main/java/com/google/samples/apps/nowinandroid/data/fake/FakeData.kt") ktlint(libs.versions.ktlint.get()).userData([android: "true"]) licenseHeaderFile rootProject.file('spotless/copyright.kt') } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a85970c79..f256673ff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,11 +22,13 @@ kotlinxCoroutines = "1.6.0" kotlinxCoroutinesTest = "1.6.0" kotlinxDatetime = "0.3.1" kotlinxSerializationJson = "1.3.1" +ksp = "1.6.0-1.0.1" ktlint = "0.43.0" material3 = "1.5.0-alpha05" mockk = "1.12.1" protobuf = "3.19.1" protobufPlugin = "0.8.18" +room = "2.4.1" spotless = "6.0.0" turbine = "0.7.0" @@ -71,7 +73,10 @@ mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" } protobuf-kotlin-lite = { group = "com.google.protobuf", name = "protobuf-kotlin-lite", version.ref = "protobuf" } turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" } +room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } +room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } [plugins] spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } diff --git a/settings.gradle b/settings.gradle index 676708897..e24e3409c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -23,6 +23,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + gradlePluginPortal() } } rootProject.name = "nowinandroid"