diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index 836389097..ec268a033 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -15,10 +15,10 @@ android { defaultConfig { applicationId "app.omnivore.omnivore" - minSdk 23 + minSdk 26 targetSdk 33 - versionCode 16 - versionName "0.0.16" + versionCode 19 + versionName "0.0.19" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -143,6 +143,11 @@ dependencies { implementation 'com.segment.analytics.kotlin:android:1.10.0' implementation 'io.intercom.android:intercom-sdk-base:14.0.0' + + // Room Deps + implementation "androidx.room:room-runtime:$room_version" + annotationProcessor "androidx.room:room-compiler:$room_version" + kapt "androidx.room:room-compiler:$room_version" } apollo { diff --git a/android/Omnivore/app/src/main/graphql/ArchiveLinkedItem.graphql b/android/Omnivore/app/src/main/graphql/ArchiveSavedItem.graphql similarity index 100% rename from android/Omnivore/app/src/main/graphql/ArchiveLinkedItem.graphql rename to android/Omnivore/app/src/main/graphql/ArchiveSavedItem.graphql diff --git a/android/Omnivore/app/src/main/graphql/DeleteLinkedItem.graphql b/android/Omnivore/app/src/main/graphql/DeleteSavedItem.graphql similarity index 100% rename from android/Omnivore/app/src/main/graphql/DeleteLinkedItem.graphql rename to android/Omnivore/app/src/main/graphql/DeleteSavedItem.graphql diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt index ef526af60..a395d831f 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/AppModule.kt @@ -1,7 +1,9 @@ package app.omnivore.omnivore import android.content.Context +import androidx.room.Room import app.omnivore.omnivore.networking.Networker +import app.omnivore.omnivore.persistence.AppDatabase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -26,4 +28,8 @@ object AppModule { @Singleton @Provides fun provideAnalytics(@ApplicationContext app: Context) = EventTracker(app) + + @Singleton + @Provides + fun provideDataService(@ApplicationContext app: Context) = DataService(app) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/DataService.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/DataService.kt new file mode 100644 index 000000000..651b7311a --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/DataService.kt @@ -0,0 +1,15 @@ +package app.omnivore.omnivore + +import android.content.Context +import androidx.room.Room +import app.omnivore.omnivore.persistence.AppDatabase +import javax.inject.Inject + +class DataService @Inject constructor( + context: Context +) { + val db = Room.databaseBuilder( + context, + AppDatabase::class.java, "omnivore-database" + ).build() +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt index abe2cec31..7cb09dfca 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt @@ -14,7 +14,7 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import app.omnivore.omnivore.ui.auth.LoginViewModel -import app.omnivore.omnivore.ui.home.HomeViewModel +import app.omnivore.omnivore.ui.library.LibraryViewModel import app.omnivore.omnivore.ui.reader.WebReaderViewModel import app.omnivore.omnivore.ui.root.RootView import app.omnivore.omnivore.ui.theme.OmnivoreTheme @@ -31,7 +31,7 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) val loginViewModel: LoginViewModel by viewModels() - val homeViewModel: HomeViewModel by viewModels() + val libraryViewModel: LibraryViewModel by viewModels() val webReaderViewModel: WebReaderViewModel by viewModels() val context = this @@ -53,7 +53,7 @@ class MainActivity : ComponentActivity() { .fillMaxSize() .background(color = Color.Black) ) { - RootView(loginViewModel, homeViewModel, webReaderViewModel) + RootView(loginViewModel, libraryViewModel, webReaderViewModel) } } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Routes.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Routes.kt index 65a4fce36..d30d337a6 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Routes.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Routes.kt @@ -1,7 +1,7 @@ package app.omnivore.omnivore sealed class Routes(val route: String) { - object Home : Routes("Home") + object Library : Routes("Library") object WebAppReader : Routes("WebAppReader") object Settings: Routes("Settings") } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/Highlight.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/Highlight.kt deleted file mode 100644 index 0a7c90423..000000000 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/Highlight.kt +++ /dev/null @@ -1,15 +0,0 @@ -package app.omnivore.omnivore.models - -data class Highlight( - val id: String, - val shortId: String, - val quote: String, - val prefix: String?, - val suffix: String?, - val patch: String, - val annotation: String?, - val createdAt: Any?, - val updatedAt: Any?, - val createdByMe : Boolean, -) - diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/LinkedItem.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/LinkedItem.kt deleted file mode 100644 index 960348b8b..000000000 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/LinkedItem.kt +++ /dev/null @@ -1,34 +0,0 @@ -package app.omnivore.omnivore.models - -import androidx.core.net.toUri - -data class LinkedItem( - val id: String, - val title: String, - val createdAt: Any, - val savedAt: Any, - val readAt: Any?, - val updatedAt: Any?, - val readingProgress: Double, - val readingProgressAnchor: Int, - val imageURLString: String?, - val pageURLString: String, - val descriptionText: String?, - val publisherURLString: String?, - val siteName: String?, - val author: String?, - val publishDate: Any?, - val slug: String, - val isArchived: Boolean, - val contentReader: String?, - val content: String? -) { - fun publisherDisplayName(): String? { - return publisherURLString?.toUri()?.host - } - - fun isPDF(): Boolean { - val hasPDFSuffix = pageURLString.endsWith("pdf") - return contentReader == "PDF" || hasPDFSuffix - } -} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/LinkedItemLabel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/LinkedItemLabel.kt deleted file mode 100644 index 69da9aee8..000000000 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/LinkedItemLabel.kt +++ /dev/null @@ -1,9 +0,0 @@ -package app.omnivore.omnivore.models - -data class LinkedItemLabel( - val id: String, - val name: String, - val color: String, - val createdAt: Any?, - val labelDescription: String?, -) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/Viewer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/Viewer.kt deleted file mode 100644 index 505068dd4..000000000 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/Viewer.kt +++ /dev/null @@ -1,9 +0,0 @@ -package app.omnivore.omnivore.models - -data class Viewer( - val id: String, - val name: String, - val username: String, - val pictureUrl: String?, -) - diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/HighlightMutations.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/HighlightMutations.kt index 4bc72d7fa..866b694e3 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/HighlightMutations.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/HighlightMutations.kt @@ -8,10 +8,10 @@ import app.omnivore.omnivore.graphql.generated.UpdateHighlightMutation import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput import app.omnivore.omnivore.graphql.generated.type.MergeHighlightInput import app.omnivore.omnivore.graphql.generated.type.UpdateHighlightInput -import app.omnivore.omnivore.models.Highlight +import app.omnivore.omnivore.persistence.entities.Highlight import com.apollographql.apollo3.api.Optional import com.google.gson.Gson -import com.pspdfkit.annotations.HighlightAnnotation +import java.time.LocalDate data class CreateHighlightParams( val shortId: String?, @@ -137,6 +137,8 @@ suspend fun Networker.createHighlight(input: CreateHighlightInput): Highlight? { val createdHighlight = result.data?.createHighlight?.onCreateHighlightSuccess?.highlight if (createdHighlight != null) { +// val updatedAtString = createdHighlight.highlightFields.updatedAt as? String + return Highlight( id = createdHighlight.highlightFields.id, shortId = createdHighlight.highlightFields.shortId, @@ -146,8 +148,10 @@ suspend fun Networker.createHighlight(input: CreateHighlightInput): Highlight? { patch = createdHighlight.highlightFields.patch, annotation = createdHighlight.highlightFields.annotation, createdAt = null, // TODO: update gql query to get this - updatedAt = createdHighlight.highlightFields.updatedAt, + updatedAt = null, // TODO: fix updatedAtString?.let { LocalDate.parse(it) }, createdByMe = createdHighlight.highlightFields.createdByMe, + markedForDeletion = false, + serverSyncStatus = 1 // TODO: create enum for this ) } else { return null diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/LinkedItemMutations.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemMutations.kt similarity index 69% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/LinkedItemMutations.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemMutations.kt index f1a8ac87a..67ead474e 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/LinkedItemMutations.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemMutations.kt @@ -5,7 +5,7 @@ import app.omnivore.omnivore.graphql.generated.SetLinkArchivedMutation import app.omnivore.omnivore.graphql.generated.type.ArchiveLinkInput import app.omnivore.omnivore.graphql.generated.type.SetBookmarkArticleInput -suspend fun Networker.deleteLinkedItem(itemID: String): Boolean { +suspend fun Networker.deleteSavedItem(itemID: String): Boolean { return try { val input = SetBookmarkArticleInput(itemID, false) val result = authenticatedApolloClient().mutation(SetBookmarkArticleMutation(input)).execute() @@ -15,15 +15,15 @@ suspend fun Networker.deleteLinkedItem(itemID: String): Boolean { } } -suspend fun Networker.archiveLinkedItem(itemID: String): Boolean { - return updateArchiveStatusLinkedItem(itemID, true) +suspend fun Networker.archiveSavedItem(itemID: String): Boolean { + return updateArchiveStatusSavedItem(itemID, true) } -suspend fun Networker.unarchiveLinkedItem(itemID: String): Boolean { - return updateArchiveStatusLinkedItem(itemID, false) +suspend fun Networker.unarchiveSavedItem(itemID: String): Boolean { + return updateArchiveStatusSavedItem(itemID, false) } -private suspend fun Networker.updateArchiveStatusLinkedItem(itemID: String, setAsArchived: Boolean): Boolean { +private suspend fun Networker.updateArchiveStatusSavedItem(itemID: String, setAsArchived: Boolean): Boolean { return try { val input = ArchiveLinkInput(setAsArchived, itemID) val result = authenticatedApolloClient().mutation(SetLinkArchivedMutation(input)).execute() diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/LinkedItemQuery.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemQuery.kt similarity index 57% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/LinkedItemQuery.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemQuery.kt index 79e6c13ad..107d17e12 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/LinkedItemQuery.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SavedItemQuery.kt @@ -1,44 +1,46 @@ package app.omnivore.omnivore.networking import app.omnivore.omnivore.graphql.generated.GetArticleQuery -import app.omnivore.omnivore.models.Highlight -import app.omnivore.omnivore.models.LinkedItem -import app.omnivore.omnivore.models.LinkedItemLabel +import app.omnivore.omnivore.persistence.entities.SavedItem +import app.omnivore.omnivore.persistence.entities.SavedItemLabel +import app.omnivore.omnivore.persistence.entities.Highlight -data class LinkedItemQueryResponse( - val item: LinkedItem?, +data class SavedItemQueryResponse( + val item: SavedItem?, val highlights: List, - val labels: List + val labels: List ) { companion object { - fun emptyResponse(): LinkedItemQueryResponse { - return LinkedItemQueryResponse(null, listOf(), listOf()) + fun emptyResponse(): SavedItemQueryResponse { + return SavedItemQueryResponse(null, listOf(), listOf()) } } } -suspend fun Networker.linkedItem(slug: String): LinkedItemQueryResponse { +suspend fun Networker.savedItem(slug: String): SavedItemQueryResponse { try { val result = authenticatedApolloClient().query( GetArticleQuery(slug = slug) ).execute() val article = result.data?.article?.onArticleSuccess?.article - ?: return LinkedItemQueryResponse.emptyResponse() + ?: return SavedItemQueryResponse.emptyResponse() val labels = article.labels ?: listOf() - val linkedItemLabels = labels.map { - LinkedItemLabel( + val savedItemLabels = labels.map { + SavedItemLabel( id = it.labelFields.id, name = it.labelFields.name, color = it.labelFields.color, - createdAt = it.labelFields.createdAt, + createdAt = it.labelFields.createdAt as String?, labelDescription = it.labelFields.description ) } val highlights = article.highlights.map { +// val updatedAtString = it.highlightFields.updatedAt as? String + Highlight( id = it.highlightFields.id, shortId = it.highlightFields.shortId, @@ -48,20 +50,22 @@ suspend fun Networker.linkedItem(slug: String): LinkedItemQueryResponse { patch = it.highlightFields.patch, annotation = it.highlightFields.annotation, createdAt = null, // TODO: update gql query to get this - updatedAt = it.highlightFields.updatedAt, + updatedAt = null, //updatedAtString?.let { str -> LocalDate.parse(str) }, TODO: fix date parsing createdByMe = it.highlightFields.createdByMe, + markedForDeletion = false, + serverSyncStatus = 1 // TODO: create enum for this ) } // TODO: handle errors - val linkedItem = LinkedItem( + val savedItem = SavedItem( id = article.articleFields.id, title = article.articleFields.title, - createdAt = article.articleFields.createdAt, - savedAt = article.articleFields.savedAt, - readAt = article.articleFields.readAt, - updatedAt = article.articleFields.updatedAt, + createdAt = article.articleFields.createdAt as String, + savedAt = article.articleFields.savedAt as String, + readAt = article.articleFields.readAt as String?, + updatedAt = article.articleFields.updatedAt as String?, readingProgress = article.articleFields.readingProgressPercent, readingProgressAnchor = article.articleFields.readingProgressAnchorIndex, imageURLString = article.articleFields.image, @@ -70,15 +74,15 @@ suspend fun Networker.linkedItem(slug: String): LinkedItemQueryResponse { publisherURLString = article.articleFields.originalArticleUrl, siteName = article.articleFields.siteName, author = article.articleFields.author, - publishDate = article.articleFields.publishedAt, + publishDate = article.articleFields.publishedAt as String?, slug = article.articleFields.slug, isArchived = article.articleFields.isArchived, contentReader = article.articleFields.contentReader.rawValue, content = article.articleFields.content ) - return LinkedItemQueryResponse(item = linkedItem, highlights, labels = linkedItemLabels) + return SavedItemQueryResponse(item = savedItem, highlights, labels = savedItemLabels) } catch (e: java.lang.Exception) { - return LinkedItemQueryResponse(item = null, listOf(), labels = listOf()) + return SavedItemQueryResponse(item = null, listOf(), labels = listOf()) } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SearchQuery.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SearchQuery.kt index 1716ad54e..429030f6c 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SearchQuery.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/SearchQuery.kt @@ -2,12 +2,13 @@ package app.omnivore.omnivore.networking import app.omnivore.omnivore.graphql.generated.SearchQuery import app.omnivore.omnivore.graphql.generated.TypeaheadSearchQuery -import app.omnivore.omnivore.models.LinkedItem +import app.omnivore.omnivore.persistence.entities.SavedItem +import app.omnivore.omnivore.persistence.entities.SavedItemCardData import com.apollographql.apollo3.api.Optional data class SearchQueryResponse( val cursor: String?, - val items: List + val cardsData: List ) suspend fun Networker.typeaheadSearch( @@ -20,31 +21,21 @@ suspend fun Networker.typeaheadSearch( val itemList = result.data?.typeaheadSearch?.onTypeaheadSearchSuccess?.items ?: listOf() - val items = itemList.map { - LinkedItem( + val cardsData = itemList.map { + SavedItemCardData( id = it.id, - title = it.title, - createdAt = "", - savedAt = "", - readAt = "", - updatedAt = "", - readingProgress = 0.0, - readingProgressAnchor = 0, - imageURLString = null, - pageURLString = "", - descriptionText = "", - publisherURLString = "", - siteName = it.siteName, - author = "", - publishDate = null, slug = it.slug, + publisherURLString = "", + title = it.title, + author = "", + imageURLString = null, isArchived = false, + pageURLString = "", contentReader = null, - content = null ) } - return SearchQueryResponse(null, items) + return SearchQueryResponse(null, cardsData) } catch (e: java.lang.Exception) { return SearchQueryResponse(null, listOf()) } @@ -68,31 +59,21 @@ suspend fun Networker.search( val newCursor = result.data?.search?.onSearchSuccess?.pageInfo?.endCursor val itemList = result.data?.search?.onSearchSuccess?.edges ?: listOf() - val items = itemList.map { - LinkedItem( + val cardsData = itemList.map { + SavedItemCardData( id = it.node.id, - title = it.node.title, - createdAt = it.node.createdAt, - savedAt = it.node.savedAt, - readAt = it.node.readAt, - updatedAt = it.node.updatedAt, - readingProgress = it.node.readingProgressPercent, - readingProgressAnchor = it.node.readingProgressAnchorIndex, - imageURLString = it.node.image, - pageURLString = it.node.url, - descriptionText = it.node.description, - publisherURLString = it.node.originalArticleUrl, - siteName = it.node.siteName, - author = it.node.author, - publishDate = it.node.publishedAt, slug = it.node.slug, + publisherURLString = it.node.originalArticleUrl, + title = it.node.title, + author = it.node.author, + imageURLString = it.node.image, isArchived = it.node.isArchived, + pageURLString = it.node.url, contentReader = it.node.contentReader.rawValue, - content = null ) } - return SearchQueryResponse(newCursor, items) + return SearchQueryResponse(newCursor, cardsData) } catch (e: java.lang.Exception) { return SearchQueryResponse(null, listOf()) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ViewerQuery.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ViewerQuery.kt index d0c7ec45a..d27c5fc15 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ViewerQuery.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/ViewerQuery.kt @@ -1,7 +1,7 @@ package app.omnivore.omnivore.networking import app.omnivore.omnivore.graphql.generated.ViewerQuery -import app.omnivore.omnivore.models.Viewer +import app.omnivore.omnivore.persistence.entities.Viewer suspend fun Networker.viewer(): Viewer? { try { @@ -10,10 +10,10 @@ suspend fun Networker.viewer(): Viewer? { return if (me != null) { Viewer( - id = me.id, + userID = me.id, name = me.name, username = me.profile.username, - pictureUrl = me.profile.pictureUrl + profileImageURL = me.profile.pictureUrl ) } else { null diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/AppDatabase.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/AppDatabase.kt new file mode 100644 index 000000000..7d9d66b94 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/AppDatabase.kt @@ -0,0 +1,20 @@ +package app.omnivore.omnivore.persistence + +import androidx.room.Database +import androidx.room.RoomDatabase +import app.omnivore.omnivore.persistence.entities.SavedItem +import app.omnivore.omnivore.persistence.entities.SavedItemDao +import app.omnivore.omnivore.persistence.entities.Viewer +import app.omnivore.omnivore.persistence.entities.ViewerDao + +@Database( + entities = [ + Viewer::class, + SavedItem::class + ], + version = 1 +) +abstract class AppDatabase : RoomDatabase() { + abstract fun viewerDao(): ViewerDao + abstract fun savedItemDao(): SavedItemDao +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/BaseDao.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/BaseDao.kt new file mode 100644 index 000000000..e422079ae --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/BaseDao.kt @@ -0,0 +1,42 @@ +package app.omnivore.omnivore.persistence + +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Update + + +interface BaseDao { + + /** + * Insert an object in the database. + * + * @param obj the object to be inserted. + */ + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(obj: T) + + /** + * Insert an array of objects in the database. + * + * @param obj the objects to be inserted. + */ + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(vararg obj: T) + + /** + * Update an object from the database. + * + * @param obj the object to be updated + */ + @Update + fun update(obj: T) + + /** + * Delete an object from the database + * + * @param obj the object to be deleted + */ + @Delete + fun delete(obj: T) +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/Highlight.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/Highlight.kt new file mode 100644 index 000000000..7b2331330 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/Highlight.kt @@ -0,0 +1,26 @@ +package app.omnivore.omnivore.persistence.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.time.LocalDate +import java.util.Date + +@Entity +data class Highlight( + @PrimaryKey val id: String, + val annotation: String?, + val createdAt: Date?, + val createdByMe: Boolean, + val markedForDeletion: Boolean, // default false + val patch: String, + val prefix: String?, + val quote: String, + val serverSyncStatus: Int, // default 0 + val shortId: String, + val suffix: String?, + val updatedAt: LocalDate? + + // has many SavedItemLabels (inverse: labels have many highlights) + // has one savedItem (inverse: savedItem has many highlights + // has a UserProfile (no inverse) +) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/NewsletterEmail.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/NewsletterEmail.kt new file mode 100644 index 000000000..2920367a1 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/NewsletterEmail.kt @@ -0,0 +1,12 @@ +package app.omnivore.omnivore.persistence.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +data class NewsletterEmail( + @PrimaryKey val userID: String, + val confirmationCode: String?, + val email: String?, + val emailID: String? +) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/RecentSearchItem.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/RecentSearchItem.kt new file mode 100644 index 000000000..2da231121 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/RecentSearchItem.kt @@ -0,0 +1,11 @@ +package app.omnivore.omnivore.persistence.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +data class RecentSearchItem( + @PrimaryKey val id: String, + val savedAt: String?, + val term: String? +) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/Recommendation.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/Recommendation.kt new file mode 100644 index 000000000..2c41c9473 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/Recommendation.kt @@ -0,0 +1,15 @@ +package app.omnivore.omnivore.persistence.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +data class Recommendation( + @PrimaryKey val groupID: String, + val name: String?, + val note: String?, + val recommendedAt: String? +) + +// hasOne SavedItem +// hasOne UserProfile diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/RecommendationGroup.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/RecommendationGroup.kt new file mode 100644 index 000000000..4ea5f5051 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/RecommendationGroup.kt @@ -0,0 +1,18 @@ +package app.omnivore.omnivore.persistence.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +data class RecommendationGroup( + @PrimaryKey val id: String, + val name: String?, + val canPost: Boolean, + val canSeeMembers: Boolean, + val inviteURL: String?, + val createdAt: String?, + val updatedAt: String? +) + +// hasMany admins (Viewer) +// hasMany members (Viewer) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt new file mode 100644 index 000000000..33cc02ca0 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt @@ -0,0 +1,115 @@ +package app.omnivore.omnivore.persistence.entities + +import androidx.core.net.toUri +import androidx.room.* +import app.omnivore.omnivore.persistence.BaseDao + +@Entity +data class SavedItem( + @PrimaryKey val id: String, + val title: String, + val createdAt: String, + val savedAt: String, + val readAt: String?, + val updatedAt: String?, + val readingProgress: Double, + val readingProgressAnchor: Int, + val imageURLString: String?, + val pageURLString: String, + val descriptionText: String?, + val publisherURLString: String?, + val siteName: String?, + val author: String?, + val publishDate: String?, + val slug: String, + val isArchived: Boolean, + val contentReader: String? = null, + val content: String? = null, + val createdId: String? = null, + val htmlContent: String? = null, + val language: String? = null, + val listenPositionIndex: Int? = null, + val listenPositionOffset: Double? = null, + val listenPositionTime: Double? = null, + val localPDF: String? = null, + val onDeviceImageURLString: String? = null, + val originalHtml: String? = null, + @ColumnInfo(typeAffinity = ColumnInfo.BLOB) val pdfData: ByteArray? = null, + val serverSyncStatus: Int = 0, // TODO: implement, + val tempPDFURL: String? = null + +// hasMany highlights +// hasMany labels +// has Many recommendations (rec has one savedItem) +) { + fun publisherDisplayName(): String? { + return publisherURLString?.toUri()?.host + } + + fun isPDF(): Boolean { + val hasPDFSuffix = pageURLString.endsWith("pdf") + return contentReader == "PDF" || hasPDFSuffix + } + + fun asSavedItemCardData(): SavedItemCardData { + return SavedItemCardData( + id = id, + slug = slug, + publisherURLString = publisherURLString, + title = title, + author = author, + imageURLString = imageURLString, + isArchived = isArchived, + pageURLString = pageURLString, + contentReader = contentReader, + ) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SavedItem + + if (id != other.id) return false + + return true + } + + override fun hashCode(): Int { + return id.hashCode() + } +} + +data class SavedItemCardData( + val id: String, + val slug: String, + val publisherURLString: String?, + val title: String, + val author: String?, + val imageURLString: String?, + val isArchived: Boolean, + val pageURLString: String, + val contentReader: String? +) { + fun publisherDisplayName(): String? { + return publisherURLString?.toUri()?.host + } + + fun isPDF(): Boolean { + val hasPDFSuffix = pageURLString.endsWith("pdf") + return contentReader == "PDF" || hasPDFSuffix + } +} + +@Dao +interface SavedItemDao { + @Query("SELECT id, slug, publisherURLString, title, author, imageURLString, isArchived, pageURLString, contentReader FROM SavedItem") + fun getLibraryData(): List + + @Query("SELECT * FROM savedItem") + fun getAll(): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertAll(items: List) +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItemLabel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItemLabel.kt new file mode 100644 index 000000000..14a0bbdf0 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItemLabel.kt @@ -0,0 +1,17 @@ +package app.omnivore.omnivore.persistence.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +data class SavedItemLabel( + @PrimaryKey val id: String, + val name: String, + val color: String, + val createdAt: String?, + val labelDescription: String?, + val serverSyncStatus: Int = 0 +) + +// has many highlights +// has many savedItems diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/UserProfile.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/UserProfile.kt new file mode 100644 index 000000000..12408f40c --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/UserProfile.kt @@ -0,0 +1,13 @@ +package app.omnivore.omnivore.persistence.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +data class UserProfile( + @PrimaryKey val userID: String, + val name: String?, + val username: String?, + val profileImageURL: String?, +) + diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/Viewer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/Viewer.kt new file mode 100644 index 000000000..5fb2d329d --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/Viewer.kt @@ -0,0 +1,27 @@ +package app.omnivore.omnivore.persistence.entities + +import androidx.room.* +import app.omnivore.omnivore.persistence.BaseDao + +@Entity +data class Viewer( + @PrimaryKey val userID: String, + val name: String, + val username: String, + val profileImageURL: String?, +) + +@Dao +interface ViewerDao { + @Query("SELECT * FROM viewer") + fun getAll(): List + + @Query("SELECT * FROM viewer WHERE userID IN (:viewerIds)") + fun loadAllByIds(viewerIds: IntArray): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertAll(vararg viewers: Viewer) + + @Delete + fun delete(viewer: Viewer) +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt index 68a7178be..4415c5a28 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/LoginViewModel.kt @@ -4,15 +4,11 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.* -import androidx.lifecycle.viewmodel.compose.viewModel import app.omnivore.omnivore.* -import app.omnivore.omnivore.graphql.generated.SearchQuery import app.omnivore.omnivore.graphql.generated.ValidateUsernameQuery -import app.omnivore.omnivore.networking.LinkedItemQueryResponse import app.omnivore.omnivore.networking.Networker import app.omnivore.omnivore.networking.viewer import com.apollographql.apollo3.ApolloClient -import com.apollographql.apollo3.api.Optional import com.google.android.gms.auth.api.signin.GoogleSignInAccount import com.google.android.gms.common.api.ApiException import com.google.android.gms.tasks.Task @@ -100,7 +96,7 @@ class LoginViewModel @Inject constructor( viewModelScope.launch { val viewer = networker.viewer() viewer?.let { - eventTracker.registerUser(viewer.id) + eventTracker.registerUser(viewer.userID) } } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt similarity index 72% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt index c004fae7b..35a16552d 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt @@ -1,4 +1,4 @@ -package app.omnivore.omnivore.ui.home +package app.omnivore.omnivore.ui.library import android.content.Intent import androidx.compose.foundation.background @@ -20,31 +20,32 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import app.omnivore.omnivore.Routes -import app.omnivore.omnivore.models.LinkedItem -import app.omnivore.omnivore.ui.linkedItemViews.LinkedItemCard +import app.omnivore.omnivore.persistence.entities.SavedItem +import app.omnivore.omnivore.persistence.entities.SavedItemCardData +import app.omnivore.omnivore.ui.savedItemViews.SavedItemCard import app.omnivore.omnivore.ui.reader.PDFReaderActivity import kotlinx.coroutines.flow.distinctUntilChanged @OptIn(ExperimentalMaterial3Api::class) @Composable -fun HomeView( - homeViewModel: HomeViewModel, +fun LibraryView( + libraryViewModel: LibraryViewModel, navController: NavHostController ) { - val searchText: String by homeViewModel.searchTextLiveData.observeAsState("") + val searchText: String by libraryViewModel.searchTextLiveData.observeAsState("") Scaffold( topBar = { SearchBar( searchText = searchText, - onSearchTextChanged = { homeViewModel.updateSearchText(it) }, + onSearchTextChanged = { libraryViewModel.updateSearchText(it) }, onSettingsIconClick = { navController.navigate(Routes.Settings.route) } ) } ) { paddingValues -> - HomeViewContent( - homeViewModel, + LibraryViewContent( + libraryViewModel, navController, modifier = Modifier .padding( @@ -57,8 +58,8 @@ fun HomeView( @OptIn(ExperimentalMaterialApi::class) @Composable -fun HomeViewContent( - homeViewModel: HomeViewModel, +fun LibraryViewContent( + libraryViewModel: LibraryViewModel, navController: NavHostController, modifier: Modifier ) { @@ -66,11 +67,11 @@ fun HomeViewContent( val listState = rememberLazyListState() val pullRefreshState = rememberPullRefreshState( - refreshing = homeViewModel.isRefreshing, - onRefresh = { homeViewModel.refresh() } + refreshing = libraryViewModel.isRefreshing, + onRefresh = { libraryViewModel.refresh() } ) - val linkedItems: List by homeViewModel.itemsLiveData.observeAsState(listOf()) + val cardsData: List by libraryViewModel.itemsLiveData.observeAsState(listOf()) Box( modifier = Modifier @@ -86,29 +87,29 @@ fun HomeViewContent( .fillMaxSize() .padding(horizontal = 6.dp) ) { - items(linkedItems) { item -> - LinkedItemCard( - item = item, + items(cardsData) { cardData -> + SavedItemCard( + cardData = cardData, onClickHandler = { - if (item.isPDF()) { + if (cardData.isPDF()) { val intent = Intent(context, PDFReaderActivity::class.java) - intent.putExtra("LINKED_ITEM_SLUG", item.slug) + intent.putExtra("SAVED_ITEM_SLUG", cardData.slug) context.startActivity(intent) } else { - navController.navigate("WebReader/${item.slug}") + navController.navigate("WebReader/${cardData.slug}") } }, - actionHandler = { homeViewModel.handleLinkedItemAction(item.id, it) } + actionHandler = { libraryViewModel.handleSavedItemAction(cardData.id, it) } ) } } InfiniteListHandler(listState = listState) { - homeViewModel.load() + libraryViewModel.load() } PullRefreshIndicator( - refreshing = homeViewModel.isRefreshing, + refreshing = libraryViewModel.isRefreshing, state = pullRefreshState, modifier = Modifier.align(Alignment.TopCenter) ) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt similarity index 73% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt index 3e442743a..e6fecd759 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt @@ -1,4 +1,4 @@ -package app.omnivore.omnivore.ui.home +package app.omnivore.omnivore.ui.library import android.util.Log import androidx.compose.runtime.getValue @@ -7,22 +7,24 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import app.omnivore.omnivore.models.LinkedItem +import app.omnivore.omnivore.DataService import app.omnivore.omnivore.networking.* -import com.pspdfkit.analytics.Analytics +import app.omnivore.omnivore.persistence.entities.SavedItemCardData import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject @HiltViewModel -class HomeViewModel @Inject constructor( +class LibraryViewModel @Inject constructor( private val networker: Networker, + private val dataService: DataService ): ViewModel() { private var cursor: String? = null - private var items: List = listOf() - private var searchedItems: List = listOf() + private var items: List = listOf() + private var searchedItems: List = listOf() // These are used to make sure we handle search result // responses in the right order @@ -31,7 +33,7 @@ class HomeViewModel @Inject constructor( // Live Data val searchTextLiveData = MutableLiveData("") - val itemsLiveData = MutableLiveData>(listOf()) + val itemsLiveData = MutableLiveData>(listOf()) var isRefreshing by mutableStateOf(false) fun updateSearchText(text: String) { @@ -80,38 +82,44 @@ class HomeViewModel @Inject constructor( if (searchTextLiveData.value != "" || clearPreviousSearch) { val previousItems = if (clearPreviousSearch) listOf() else searchedItems - searchedItems = previousItems.plus(searchResult.items) + searchedItems = previousItems.plus(searchResult.cardsData) itemsLiveData.postValue(searchedItems) } else { - items = items.plus(searchResult.items) + items = items.plus(searchResult.cardsData) itemsLiveData.postValue(items) } +// withContext(Dispatchers.IO) { +// dataService.db.savedItemDao().insertAll(items) +// val items = dataService.db.savedItemDao().getLibraryData() +// Log.d("appDatabase", "libraryData: $items") +// } + CoroutineScope(Dispatchers.Main).launch { isRefreshing = false } } } - fun handleLinkedItemAction(itemID: String, action: LinkedItemAction) { + fun handleSavedItemAction(itemID: String, action: SavedItemAction) { when (action) { - LinkedItemAction.Delete -> { + SavedItemAction.Delete -> { removeItemFromList(itemID) viewModelScope.launch { - networker.deleteLinkedItem(itemID) + networker.deleteSavedItem(itemID) } } - LinkedItemAction.Archive -> { + SavedItemAction.Archive -> { removeItemFromList(itemID) viewModelScope.launch { - networker.archiveLinkedItem(itemID) + networker.archiveSavedItem(itemID) } } - LinkedItemAction.Unarchive -> { + SavedItemAction.Unarchive -> { removeItemFromList(itemID) viewModelScope.launch { - networker.unarchiveLinkedItem(itemID) + networker.unarchiveSavedItem(itemID) } } } @@ -135,7 +143,7 @@ class HomeViewModel @Inject constructor( } } -enum class LinkedItemAction { +enum class SavedItemAction { Delete, Archive, Unarchive diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/SearchBar.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SearchBar.kt similarity index 98% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/SearchBar.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SearchBar.kt index c4d93badc..63b0b91e9 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/SearchBar.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SearchBar.kt @@ -1,4 +1,4 @@ -package app.omnivore.omnivore.ui.home +package app.omnivore.omnivore.ui.library import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn @@ -38,7 +38,7 @@ fun SearchBar( if (showSearchField) { SearchField(searchText, onSearchTextChanged) } else { - Text("Home") + Text("Library") } }, colors = TopAppBarDefaults.smallTopAppBarColors( diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt index 65c71ad36..d6249cd21 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt @@ -19,7 +19,7 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.DrawableCompat import androidx.lifecycle.Observer import app.omnivore.omnivore.R -import app.omnivore.omnivore.models.Highlight +import app.omnivore.omnivore.persistence.entities.Highlight import com.pspdfkit.annotations.Annotation import com.pspdfkit.annotations.HighlightAnnotation import com.pspdfkit.configuration.PdfConfiguration @@ -78,7 +78,7 @@ class PDFReaderActivity: AppCompatActivity(), DocumentListener, TextSelectionMan // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer. viewModel.pdfReaderParamsLiveData.observe(this, pdfParamsObserver) - val slug = intent.getStringExtra("LINKED_ITEM_SLUG") ?: "" + val slug = intent.getStringExtra("SAVED_ITEM_SLUG") ?: "" viewModel.loadItem(slug, this) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt index 620e333b0..d313994f6 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt @@ -10,7 +10,7 @@ import app.omnivore.omnivore.DatastoreRepository import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput import app.omnivore.omnivore.graphql.generated.type.MergeHighlightInput import app.omnivore.omnivore.graphql.generated.type.UpdateHighlightInput -import app.omnivore.omnivore.models.LinkedItem +import app.omnivore.omnivore.persistence.entities.SavedItem import app.omnivore.omnivore.networking.* import com.apollographql.apollo3.api.Optional import com.google.gson.Gson @@ -29,7 +29,7 @@ import java.util.* import javax.inject.Inject data class PDFReaderParams( - val item: LinkedItem, + val item: SavedItem, val articleContent: ArticleContent, val localFileUri: Uri ) @@ -45,7 +45,7 @@ class PDFReaderViewModel @Inject constructor( fun loadItem(slug: String, context: Context) { viewModelScope.launch { - val articleQueryResult = networker.linkedItem(slug) + val articleQueryResult = networker.savedItem(slug) val article = articleQueryResult.item ?: return@launch diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt index 73b707d73..25c173aab 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt @@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.verticalScroll import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.* import androidx.compose.runtime.* @@ -29,14 +28,12 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.content.ContextCompat.getSystemService import app.omnivore.omnivore.R -import app.omnivore.omnivore.ui.linkedItemViews.LinkedItemContextMenu +import app.omnivore.omnivore.ui.savedItemViews.SavedItemContextMenu import com.google.gson.Gson import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.json.JSONObject import java.util.* import kotlin.math.roundToInt @@ -112,11 +109,11 @@ fun WebReaderLoadingContainer(slug: String, webReaderViewModel: WebReaderViewMod contentDescription = null ) } - LinkedItemContextMenu( + SavedItemContextMenu( isExpanded = isMenuExpanded, isArchived = webReaderParams!!.item.isArchived, onDismiss = { isMenuExpanded = false }, - actionHandler = { webReaderViewModel.handleLinkedItemAction(webReaderParams!!.item.id, it) } + actionHandler = { webReaderViewModel.handleSavedItemAction(webReaderParams!!.item.id, it) } ) } ) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderContent.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderContent.kt index 55b1b85de..fccd4038c 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderContent.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderContent.kt @@ -1,8 +1,8 @@ package app.omnivore.omnivore.ui.reader import android.util.Log -import app.omnivore.omnivore.models.Highlight -import app.omnivore.omnivore.models.LinkedItem +import app.omnivore.omnivore.persistence.entities.SavedItem +import app.omnivore.omnivore.persistence.entities.Highlight import com.google.gson.Gson enum class WebFont(val displayText: String, val rawValue: String) { @@ -39,7 +39,7 @@ data class ArticleContent( data class WebReaderContent( val preferences: WebPreferences, - val item: LinkedItem, + val item: SavedItem, val articleContent: ArticleContent, ) { fun styledContent(): String { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt index baaadd34a..fe6c1b103 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt @@ -2,15 +2,14 @@ package app.omnivore.omnivore.ui.reader import android.util.Log import androidx.compose.foundation.ScrollState -import androidx.compose.ui.unit.Density import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.omnivore.omnivore.DatastoreKeys import app.omnivore.omnivore.DatastoreRepository -import app.omnivore.omnivore.models.LinkedItem +import app.omnivore.omnivore.persistence.entities.SavedItem import app.omnivore.omnivore.networking.* -import app.omnivore.omnivore.ui.home.LinkedItemAction +import app.omnivore.omnivore.ui.library.SavedItemAction import com.google.gson.Gson import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope @@ -21,7 +20,7 @@ import java.util.* import javax.inject.Inject data class WebReaderParams( - val item: LinkedItem, + val item: SavedItem, val articleContent: ArticleContent ) @@ -48,7 +47,7 @@ class WebReaderViewModel @Inject constructor( fun loadItem(slug: String) { viewModelScope.launch { - val articleQueryResult = networker.linkedItem(slug) + val articleQueryResult = networker.savedItem(slug) val article = articleQueryResult.item ?: return@launch @@ -65,33 +64,33 @@ class WebReaderViewModel @Inject constructor( } } - fun handleLinkedItemAction(itemID: String, action: LinkedItemAction) { + fun handleSavedItemAction(itemID: String, action: SavedItemAction) { when (action) { - LinkedItemAction.Delete -> { + SavedItemAction.Delete -> { viewModelScope.launch { - networker.deleteLinkedItem(itemID) - popToHomeView(itemID) + networker.deleteSavedItem(itemID) + popToLibraryView(itemID) } } - LinkedItemAction.Archive -> { + SavedItemAction.Archive -> { viewModelScope.launch { - networker.archiveLinkedItem(itemID) - popToHomeView(itemID) + networker.archiveSavedItem(itemID) + popToLibraryView(itemID) } } - LinkedItemAction.Unarchive -> { + SavedItemAction.Unarchive -> { viewModelScope.launch { - networker.unarchiveLinkedItem(itemID) - popToHomeView(itemID) + networker.unarchiveSavedItem(itemID) + popToLibraryView(itemID) } } } } - private fun popToHomeView(itemID: String) { + private fun popToLibraryView(itemID: String) { CoroutineScope(Dispatchers.Main).launch { - // TODO: pop to home - Log.d("maxx", "should pop to home and remove item with ID: $itemID") + // TODO: pop to library + Log.d("maxx", "should pop to library and remove item with ID: $itemID") } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt index b0abce8d2..e22d3ac18 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt @@ -16,15 +16,15 @@ import androidx.navigation.compose.rememberNavController import app.omnivore.omnivore.Routes import app.omnivore.omnivore.ui.auth.LoginViewModel import app.omnivore.omnivore.ui.auth.WelcomeScreen -import app.omnivore.omnivore.ui.home.HomeView -import app.omnivore.omnivore.ui.home.HomeViewModel +import app.omnivore.omnivore.ui.library.LibraryView +import app.omnivore.omnivore.ui.library.LibraryViewModel import app.omnivore.omnivore.ui.reader.* import com.google.accompanist.systemuicontroller.rememberSystemUiController @Composable fun RootView( loginViewModel: LoginViewModel, - homeViewModel: HomeViewModel, + libraryViewModel: LibraryViewModel, webReaderViewModel: WebReaderViewModel ) { val hasAuthToken: Boolean by loginViewModel.hasAuthTokenLiveData.observeAsState(false) @@ -47,7 +47,7 @@ fun RootView( if (hasAuthToken) { PrimaryNavigator( loginViewModel = loginViewModel, - homeViewModel = homeViewModel, + libraryViewModel = libraryViewModel, webReaderViewModel = webReaderViewModel ) } else { @@ -66,15 +66,15 @@ fun RootView( @Composable fun PrimaryNavigator( loginViewModel: LoginViewModel, - homeViewModel: HomeViewModel, + libraryViewModel: LibraryViewModel, webReaderViewModel: WebReaderViewModel ) { val navController = rememberNavController() - NavHost(navController = navController, startDestination = Routes.Home.route) { - composable(Routes.Home.route) { - HomeView( - homeViewModel = homeViewModel, + NavHost(navController = navController, startDestination = Routes.Library.route) { + composable(Routes.Library.route) { + LibraryView( + libraryViewModel = libraryViewModel, navController = navController ) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/linkedItemViews/LinkedItemCard.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt similarity index 72% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/linkedItemViews/LinkedItemCard.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt index 775ed6be7..d179667e6 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/linkedItemViews/LinkedItemCard.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt @@ -1,4 +1,4 @@ -package app.omnivore.omnivore.ui.linkedItemViews +package app.omnivore.omnivore.ui.savedItemViews import androidx.compose.foundation.* import androidx.compose.foundation.layout.* @@ -12,15 +12,16 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import app.omnivore.omnivore.models.LinkedItem -import app.omnivore.omnivore.ui.home.LinkedItemAction +import app.omnivore.omnivore.persistence.entities.SavedItem +import app.omnivore.omnivore.persistence.entities.SavedItemCardData +import app.omnivore.omnivore.ui.library.SavedItemAction import coil.compose.rememberAsyncImagePainter @OptIn(ExperimentalFoundationApi::class) @Composable -fun LinkedItemCard(item: LinkedItem, onClickHandler: () -> Unit, actionHandler: (LinkedItemAction) -> Unit) { +fun SavedItemCard(cardData: SavedItemCardData, onClickHandler: () -> Unit, actionHandler: (SavedItemAction) -> Unit) { var isMenuExpanded by remember { mutableStateOf(false) } - val publisherDisplayName = item.publisherDisplayName() + val publisherDisplayName = cardData.publisherDisplayName() Column { Row( @@ -42,14 +43,14 @@ fun LinkedItemCard(item: LinkedItem, onClickHandler: () -> Unit, actionHandler: .padding(end = 8.dp) ) { Text( - text = item.title, + text = cardData.title, style = MaterialTheme.typography.titleMedium, lineHeight = 20.sp ) - if (item.author != null && item.author != "") { + if (cardData.author != null && cardData.author != "") { Text( - text = "By ${item.author}", + text = "By ${cardData.author}", style = MaterialTheme.typography.bodyMedium, maxLines = 1, overflow = TextOverflow.Ellipsis @@ -66,10 +67,10 @@ fun LinkedItemCard(item: LinkedItem, onClickHandler: () -> Unit, actionHandler: } } - if (item.imageURLString != null) { + if (cardData.imageURLString != null) { Image( - painter = rememberAsyncImagePainter(item.imageURLString), - contentDescription = "Image associated with linked item", + painter = rememberAsyncImagePainter(cardData.imageURLString), + contentDescription = "Image associated with saved item", modifier = Modifier .padding(top = 6.dp) .clip(RoundedCornerShape(6.dp)) @@ -80,9 +81,9 @@ fun LinkedItemCard(item: LinkedItem, onClickHandler: () -> Unit, actionHandler: Divider(color = MaterialTheme.colorScheme.outlineVariant, thickness = 1.dp) - LinkedItemContextMenu( + SavedItemContextMenu( isExpanded = isMenuExpanded, - isArchived = item.isArchived, + isArchived = cardData.isArchived, onDismiss = { isMenuExpanded = false }, actionHandler = actionHandler ) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/linkedItemViews/LinkedItemContextMenu.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt similarity index 77% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/linkedItemViews/LinkedItemContextMenu.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt index ea11a4038..7725d62d7 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/linkedItemViews/LinkedItemContextMenu.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt @@ -1,4 +1,4 @@ -package app.omnivore.omnivore.ui.linkedItemViews +package app.omnivore.omnivore.ui.savedItemViews import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Delete @@ -8,14 +8,14 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import app.omnivore.omnivore.ui.home.LinkedItemAction +import app.omnivore.omnivore.ui.library.SavedItemAction @Composable -fun LinkedItemContextMenu( +fun SavedItemContextMenu( isExpanded: Boolean, isArchived: Boolean, onDismiss: () -> Unit, - actionHandler: (LinkedItemAction) -> Unit + actionHandler: (SavedItemAction) -> Unit ) { DropdownMenu( expanded = isExpanded, @@ -24,7 +24,7 @@ fun LinkedItemContextMenu( DropdownMenuItem( text = { Text(if (isArchived) "Unarchive" else "Archive") }, onClick = { - val action = if (isArchived) LinkedItemAction.Unarchive else LinkedItemAction.Archive + val action = if (isArchived) SavedItemAction.Unarchive else SavedItemAction.Archive actionHandler(action) onDismiss() }, @@ -38,7 +38,7 @@ fun LinkedItemContextMenu( DropdownMenuItem( text = { Text("Remove Item") }, onClick = { - actionHandler(LinkedItemAction.Delete) + actionHandler(SavedItemAction.Delete) onDismiss() }, leadingIcon = { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt index 2c47411f4..fbedadde0 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/settings/SettingsContent.kt @@ -1,14 +1,11 @@ import android.annotation.SuppressLint -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Home import androidx.compose.material3.* -import androidx.compose.material3.TopAppBarDefaults.smallTopAppBarColors import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController @@ -34,7 +31,7 @@ fun SettingsView( containerColor = MaterialTheme.colorScheme.surfaceVariant ), actions = { - IconButton(onClick = { navController.navigate(Routes.Home.route) }) { + IconButton(onClick = { navController.navigate(Routes.Library.route) }) { Icon( imageVector = Icons.Default.Home, contentDescription = null diff --git a/android/Omnivore/build.gradle b/android/Omnivore/build.gradle index 00a604ec9..6c9b942d4 100644 --- a/android/Omnivore/build.gradle +++ b/android/Omnivore/build.gradle @@ -4,6 +4,7 @@ buildscript { lifecycle_version = '2.5.1' hilt_version = '2.44.2' gradle_plugin_version = '7.3.1' + room_version = '2.4.3' } dependencies {