From 8ac5b4c8f1013f1d5c3bb082fd8150c409458a1a Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 5 May 2023 15:25:22 +0800 Subject: [PATCH 01/15] Remove shadow types used for CardData from Android types --- .../omnivore/dataService/LibraryLiveData.kt | 126 ------------------ .../omnivore/networking/TypeaheadSearch.kt | 5 - .../persistence/entities/SavedItem.kt | 65 --------- .../persistence/entities/SavedItemLabel.kt | 19 --- .../ui/library/LibraryNavigationBar.kt | 6 - .../omnivore/ui/library/LibraryView.kt | 2 - .../omnivore/ui/library/SavedItemViewModel.kt | 1 - .../omnivore/ui/library/SearchView.kt | 2 - .../ui/savedItemViews/SavedItemCard.kt | 2 - .../ui/savedItemViews/TypeaheadSearchCard.kt | 2 - 10 files changed, 230 deletions(-) delete mode 100644 android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibraryLiveData.kt diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibraryLiveData.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibraryLiveData.kt deleted file mode 100644 index b9cd3967f..000000000 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibraryLiveData.kt +++ /dev/null @@ -1,126 +0,0 @@ -package app.omnivore.omnivore.dataService - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MediatorLiveData -import app.omnivore.omnivore.persistence.entities.SavedItemCardDataWithLabels -import app.omnivore.omnivore.persistence.entities.SavedItemLabel -import app.omnivore.omnivore.ui.library.SavedItemFilter -import app.omnivore.omnivore.ui.library.SavedItemSortFilter - -fun DataService.libraryLiveData( - primaryFilter: SavedItemFilter, - sortFilter: SavedItemSortFilter, - labels: List -): LiveData> { - val mediatorLiveData = MediatorLiveData>() - val queryParams = LibraryLiveDataQueryParams.make(primaryFilter) - - val libraryLiveData = when (sortFilter) { - SavedItemSortFilter.NEWEST -> db.savedItemDao().getLibraryLiveData( - archiveFilter = queryParams.archiveFilter - ) - SavedItemSortFilter.OLDEST -> db.savedItemDao().getLibraryLiveDataSortedByOldest( - archiveFilter = queryParams.archiveFilter - ) - SavedItemSortFilter.RECENTLY_READ -> db.savedItemDao().getLibraryLiveDataSortedByRecentlyRead( - archiveFilter = queryParams.archiveFilter - ) - SavedItemSortFilter.RECENTLY_PUBLISHED -> db.savedItemDao().getLibraryLiveDataSortedByRecentlyPublished( - archiveFilter = queryParams.archiveFilter - ) - } - - mediatorLiveData.addSource(libraryLiveData) { result -> - when (primaryFilter) { - SavedItemFilter.INBOX -> { - mediatorLiveData.value = result - } - SavedItemFilter.READ_LATER -> { - mediatorLiveData.value = result.filter { item -> - !item.labels.any { it.name.lowercase() == "newsletter" } - } - } - SavedItemFilter.NEWSLETTERS -> { - mediatorLiveData.value = result.filter { item -> - item.labels.any { it.name.lowercase() == "newsletter" } - } - } - SavedItemFilter.RECOMMENDED -> { - mediatorLiveData.value = result // TODO: "recommendations.@count > 0" - } - SavedItemFilter.ALL -> { - mediatorLiveData.value = result - } - SavedItemFilter.ARCHIVED -> { - mediatorLiveData.value = result - } - SavedItemFilter.HAS_HIGHLIGHTS -> { - mediatorLiveData.value = result // TODO: "highlights.@count > 0" - } - SavedItemFilter.FILES -> { - mediatorLiveData.value = result.filter { item -> - item.cardData.contentReader == "PDF" - } - } - } - - if (labels.isNotEmpty()) { - mediatorLiveData.value = (mediatorLiveData.value ?: listOf()).filter { - it.labels.intersect(labels.toSet()).any() - } - } - } - - return mediatorLiveData -} - -private data class LibraryLiveDataQueryParams( - val archiveFilter: Int -) { - companion object { - fun make(savedItemFilter: SavedItemFilter): LibraryLiveDataQueryParams { - return when (savedItemFilter) { - SavedItemFilter.INBOX -> { - LibraryLiveDataQueryParams( - archiveFilter = 1, // Filter out items marked as archive - ) - } - SavedItemFilter.READ_LATER -> { - LibraryLiveDataQueryParams( - archiveFilter = 1, // Filter out items marked as archive - ) - } - SavedItemFilter.NEWSLETTERS -> { - LibraryLiveDataQueryParams( - archiveFilter = 1, // Filter out items marked as archive - ) - } - SavedItemFilter.RECOMMENDED -> { - LibraryLiveDataQueryParams( - archiveFilter = 1, // Filter out items marked as archive - ) - } - SavedItemFilter.ALL -> { - LibraryLiveDataQueryParams( - archiveFilter = 2, // Don't filter anything out (2 will not match anything) - ) - } - SavedItemFilter.ARCHIVED -> { - LibraryLiveDataQueryParams( - archiveFilter = 0, // Filter out items not marked as archived - ) - } - SavedItemFilter.HAS_HIGHLIGHTS -> { - LibraryLiveDataQueryParams( - archiveFilter = 1, // Filter out items marked as archive - ) - } - SavedItemFilter.FILES -> { - LibraryLiveDataQueryParams( - archiveFilter = 1, // Filter out items marked as archive - ) - } - } - } - } -} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/TypeaheadSearch.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/TypeaheadSearch.kt index 48a61bafd..1091c2889 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/TypeaheadSearch.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/networking/TypeaheadSearch.kt @@ -1,12 +1,7 @@ package app.omnivore.omnivore.networking -import app.omnivore.omnivore.graphql.generated.SearchQuery import app.omnivore.omnivore.graphql.generated.TypeaheadSearchQuery -import app.omnivore.omnivore.persistence.entities.SavedItem -import app.omnivore.omnivore.persistence.entities.SavedItemCardData -import app.omnivore.omnivore.persistence.entities.SavedItemLabel import app.omnivore.omnivore.persistence.entities.TypeaheadCardData -import com.apollographql.apollo3.api.Optional data class SearchQueryResponse( val cursor: String?, 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 index 68f4b40f5..535ee1899 100644 --- 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 @@ -2,12 +2,7 @@ package app.omnivore.omnivore.persistence.entities import androidx.core.net.toUri import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.room.* -import app.omnivore.omnivore.BuildConfig -import app.omnivore.omnivore.graphql.generated.SearchQuery -import app.omnivore.omnivore.models.ServerSyncStatus -import app.omnivore.omnivore.ui.library.SavedItemSortFilter import java.util.* @Entity @@ -69,30 +64,6 @@ data class SavedItem( } } -data class SavedItemCardData( - val savedItemId: 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?, - val savedAt: String, - val readingProgress: Double, - val wordsCount: Int? -) { - fun publisherDisplayName(): String? { - return publisherURLString?.toUri()?.host - } - - fun isPDF(): Boolean { - val hasPDFSuffix = pageURLString.endsWith("pdf") - return contentReader == "PDF" || hasPDFSuffix - } -} - data class TypeaheadCardData( val savedItemId: String, val slug: String, @@ -126,42 +97,6 @@ interface SavedItemDao { @Update fun update(savedItem: SavedItem) - @Transaction - @Query( - "SELECT ${SavedItemQueryConstants.columns} " + - "FROM SavedItem " + - "WHERE serverSyncStatus != 2 AND isArchived != :archiveFilter " + - "ORDER BY savedAt DESC" - ) - fun getLibraryLiveData(archiveFilter: Int): LiveData> - - @Transaction - @Query( - "SELECT ${SavedItemQueryConstants.columns} " + - "FROM SavedItem " + - "WHERE serverSyncStatus != 2 AND isArchived != :archiveFilter " + - "ORDER BY savedAt ASC" - ) - fun getLibraryLiveDataSortedByOldest(archiveFilter: Int): LiveData> - - @Transaction - @Query( - "SELECT ${SavedItemQueryConstants.columns} " + - "FROM SavedItem " + - "WHERE serverSyncStatus != 2 AND isArchived != :archiveFilter " + - "ORDER BY readAt DESC, savedAt DESC" - ) - fun getLibraryLiveDataSortedByRecentlyRead(archiveFilter: Int): LiveData> - - @Transaction - @Query( - "SELECT ${SavedItemQueryConstants.columns} " + - "FROM SavedItem " + - "WHERE serverSyncStatus != 2 AND isArchived != :archiveFilter " + - "ORDER BY publishDate DESC" - ) - fun getLibraryLiveDataSortedByRecentlyPublished(archiveFilter: Int): LiveData> - @Transaction @Query( "SELECT ${SavedItemQueryConstants.libraryColumns} " + 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 index 1809ddf91..dea2200ac 100644 --- 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 @@ -44,25 +44,6 @@ data class SavedItemAndSavedItemLabelCrossRef( val savedItemId: String ) -data class SavedItemWithLabels( - @Embedded val savedItem: SavedItem, - @Relation( - parentColumn = "savedItemId", - entityColumn = "savedItemLabelId", - associateBy = Junction(SavedItemAndSavedItemLabelCrossRef::class) - ) - val labels: List -) - -data class SavedItemCardDataWithLabels( - @Embedded val cardData: SavedItemCardData, - @Relation( - parentColumn = "savedItemId", - entityColumn = "savedItemLabelId", - associateBy = Junction(SavedItemAndSavedItemLabelCrossRef::class) - ) - val labels: List -) @Dao interface SavedItemAndSavedItemLabelCrossRefDao { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryNavigationBar.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryNavigationBar.kt index afd43845b..19e2a6bb8 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryNavigationBar.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryNavigationBar.kt @@ -3,13 +3,11 @@ package app.omnivore.omnivore.ui.library import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.foundation.Image import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* -import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Delete import androidx.compose.material3.* import androidx.compose.runtime.* @@ -19,14 +17,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.input.ImeAction -import androidx.lifecycle.MutableLiveData import androidx.navigation.NavHostController import app.omnivore.omnivore.R -import app.omnivore.omnivore.persistence.entities.SavedItemCardData -import app.omnivore.omnivore.persistence.entities.SavedItemCardDataWithLabels import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights @OptIn(ExperimentalMaterial3Api::class) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt index 3d9596578..1e9b49dd4 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt @@ -33,8 +33,6 @@ import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import app.omnivore.omnivore.R import app.omnivore.omnivore.Routes -import app.omnivore.omnivore.persistence.entities.SavedItemCardData -import app.omnivore.omnivore.persistence.entities.SavedItemCardDataWithLabels import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights import app.omnivore.omnivore.ui.components.LabelsSelectionSheet import app.omnivore.omnivore.ui.savedItemViews.SavedItemCard diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SavedItemViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SavedItemViewModel.kt index 1d2728d8a..199c668ce 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SavedItemViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SavedItemViewModel.kt @@ -1,7 +1,6 @@ package app.omnivore.omnivore.ui.library import androidx.lifecycle.MutableLiveData -import app.omnivore.omnivore.persistence.entities.SavedItemCardData import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights interface SavedItemViewModel { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SearchView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SearchView.kt index f2aaa71d6..15d47dcf7 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SearchView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SearchView.kt @@ -22,8 +22,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import app.omnivore.omnivore.R -import app.omnivore.omnivore.persistence.entities.SavedItemCardData -import app.omnivore.omnivore.persistence.entities.SavedItemCardDataWithLabels import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainerActivity import app.omnivore.omnivore.persistence.entities.TypeaheadCardData diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt index 1144ced03..126ac2570 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt @@ -21,8 +21,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import app.omnivore.omnivore.R -import app.omnivore.omnivore.persistence.entities.SavedItemCardData -import app.omnivore.omnivore.persistence.entities.SavedItemLabel import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights import app.omnivore.omnivore.ui.components.LabelChipColors import app.omnivore.omnivore.ui.library.LibraryViewModel diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/TypeaheadSearchCard.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/TypeaheadSearchCard.kt index 337b8afae..f74cceef8 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/TypeaheadSearchCard.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/TypeaheadSearchCard.kt @@ -17,8 +17,6 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* -import app.omnivore.omnivore.persistence.entities.SavedItemCardData -import app.omnivore.omnivore.persistence.entities.SavedItemLabel import app.omnivore.omnivore.persistence.entities.TypeaheadCardData import app.omnivore.omnivore.ui.components.LabelChipColors import app.omnivore.omnivore.ui.library.SavedItemAction From b7421950a42da06a165a86369f5d62a520045433 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 5 May 2023 16:35:41 +0800 Subject: [PATCH 02/15] Do all item db insertion in a single transaction --- .../omnivore/dataService/LibrarySync.kt | 168 ++++++++++-------- .../omnivore/persistence/AppDatabase.kt | 1 + .../persistence/entities/SavedItem.kt | 56 +++++- 3 files changed, 146 insertions(+), 79 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibrarySync.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibrarySync.kt index 3a93489d2..5d43370f1 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibrarySync.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibrarySync.kt @@ -17,24 +17,7 @@ suspend fun DataService.librarySearch(cursor: String?, query: String): SearchRes ) } - db.savedItemDao().insertAll(savedItems.map { it.savedItem }) - - val labels: MutableList = mutableListOf() - val crossRefs: MutableList = mutableListOf() - - // save labels - for (searchItem in searchResult.items) { - labels.addAll(searchItem.labels) - - val newCrossRefs = searchItem.labels.map { - SavedItemAndSavedItemLabelCrossRef(savedItemLabelId = it.savedItemLabelId, savedItemId = searchItem.item.savedItemId) - } - - crossRefs.addAll(newCrossRefs) - } - - db.savedItemLabelDao().insertAll(labels) - db.savedItemAndSavedItemLabelCrossRefDao().insertAll(crossRefs) + db.savedItemWithLabelsAndHighlightsDao().insertAll(savedItems) Log.d("sync", "found ${searchResult.items.size} items with search api. Query: $query cursor: $cursor") @@ -52,7 +35,7 @@ suspend fun DataService.sync(since: String, cursor: String?, limit: Int = 20): S ?: return SavedItemSyncResult.errorResult val savedItems = syncResult.items.map { - SavedItem( + val savedItem = SavedItem( savedItemId = it.id, title = it.title, createdAt = it.createdAt as String, @@ -74,71 +57,104 @@ suspend fun DataService.sync(since: String, cursor: String?, limit: Int = 20): S content = null, wordsCount = it.wordsCount ) - } - - db.savedItemDao().insertAll(savedItems) - - val labels: MutableList = mutableListOf() - val crossRefs: MutableList = mutableListOf() - - // save labels - for (item in syncResult.items) { - val itemLabels = (item.labels ?: listOf()).map { + val labels = it.labels?.map { label -> SavedItemLabel( - savedItemLabelId = it.labelFields.id, - name = it.labelFields.name, - color = it.labelFields.color, + savedItemLabelId = label.labelFields.id, + name = label.labelFields.name, + color = label.labelFields.color, createdAt = null, labelDescription = null ) - } - - labels.addAll(itemLabels) - - val newCrossRefs = itemLabels.map { - SavedItemAndSavedItemLabelCrossRef( - savedItemLabelId = it.savedItemLabelId, - savedItemId = item.id + } ?: listOf() + val highlights = it.highlights?.map { highlight -> + Highlight( + type = highlight.highlightFields.type.toString(), + highlightId = highlight.highlightFields.id, + annotation = highlight.highlightFields.annotation, + createdByMe = highlight.highlightFields.createdByMe, + markedForDeletion = false, + patch = highlight.highlightFields.patch, + prefix = highlight.highlightFields.prefix, + quote = highlight.highlightFields.quote, + serverSyncStatus = ServerSyncStatus.IS_SYNCED.rawValue, + shortId = highlight.highlightFields.shortId, + suffix = highlight.highlightFields.suffix, + createdAt = null, + updatedAt = highlight.highlightFields.updatedAt as String?, ) - } - - crossRefs.addAll(newCrossRefs) - } - - db.savedItemLabelDao().insertAll(labels) - db.savedItemAndSavedItemLabelCrossRefDao().insertAll(crossRefs) - - // Persist Highlights - db.highlightDao().insertAll(syncResult.items.flatMap { - it.highlights ?: listOf() - }.map { - Highlight( - type = it.highlightFields.type.toString(), - highlightId = it.highlightFields.id, - annotation = it.highlightFields.annotation, - createdByMe = it.highlightFields.createdByMe, - markedForDeletion = false, - patch = it.highlightFields.patch, - prefix = it.highlightFields.prefix, - quote = it.highlightFields.quote, - serverSyncStatus = ServerSyncStatus.IS_SYNCED.rawValue, - shortId = it.highlightFields.shortId, - suffix = it.highlightFields.suffix, - createdAt = null, - updatedAt = it.highlightFields.updatedAt as String?, + } ?: listOf() + SavedItemWithLabelsAndHighlights( + savedItem = savedItem, + labels = labels, + highlights = highlights ) - }) - - val highlightCrossRefs = syncResult.items.flatMap { - val savedItem = it - (savedItem.highlights ?: listOf()).map { - Pair(it, savedItem.id) - } - }.map { - SavedItemAndHighlightCrossRef(highlightId = it.first.highlightFields.id, savedItemId = it.second) } - db.savedItemAndHighlightCrossRefDao().insertAll(highlightCrossRefs) + db.savedItemWithLabelsAndHighlightsDao().insertAll(savedItems) +// +// db.savedItemDao().insertAll(savedItems) +// +// val labels: MutableList = mutableListOf() +// val crossRefs: MutableList = mutableListOf() +// +// // save labels +// for (item in syncResult.items) { +// val itemLabels = (item.labels ?: listOf()).map { +// SavedItemLabel( +// savedItemLabelId = it.labelFields.id, +// name = it.labelFields.name, +// color = it.labelFields.color, +// createdAt = null, +// labelDescription = null +// ) +// } +// +// labels.addAll(itemLabels) +// +// val newCrossRefs = itemLabels.map { +// SavedItemAndSavedItemLabelCrossRef( +// savedItemLabelId = it.savedItemLabelId, +// savedItemId = item.id +// ) +// } +// +// crossRefs.addAll(newCrossRefs) +// } +// +// db.savedItemLabelDao().insertAll(labels) +// db.savedItemAndSavedItemLabelCrossRefDao().insertAll(crossRefs) +// +// // Persist Highlights +// db.highlightDao().insertAll(syncResult.items.flatMap { +// it.highlights ?: listOf() +// }.map { +// Highlight( +// type = it.highlightFields.type.toString(), +// highlightId = it.highlightFields.id, +// annotation = it.highlightFields.annotation, +// createdByMe = it.highlightFields.createdByMe, +// markedForDeletion = false, +// patch = it.highlightFields.patch, +// prefix = it.highlightFields.prefix, +// quote = it.highlightFields.quote, +// serverSyncStatus = ServerSyncStatus.IS_SYNCED.rawValue, +// shortId = it.highlightFields.shortId, +// suffix = it.highlightFields.suffix, +// createdAt = null, +// updatedAt = it.highlightFields.updatedAt as String?, +// ) +// }) +// +// val highlightCrossRefs = syncResult.items.flatMap { +// val savedItem = it +// (savedItem.highlights ?: listOf()).map { +// Pair(it, savedItem.id) +// } +// }.map { +// SavedItemAndHighlightCrossRef(highlightId = it.first.highlightFields.id, savedItemId = it.second) +// } +// +// db.savedItemAndHighlightCrossRefDao().insertAll(highlightCrossRefs) Log.d("sync", "found ${syncResult.items.size} items with sync api. Since: $since") 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 index dc30a3c09..76f7efe5f 100644 --- 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 @@ -20,6 +20,7 @@ abstract class AppDatabase : RoomDatabase() { abstract fun savedItemDao(): SavedItemDao abstract fun highlightDao(): HighlightDao abstract fun savedItemLabelDao(): SavedItemLabelDao + abstract fun savedItemWithLabelsAndHighlightsDao(): SavedItemWithLabelsAndHighlightsDao abstract fun savedItemAndSavedItemLabelCrossRefDao(): SavedItemAndSavedItemLabelCrossRefDao abstract fun savedItemAndHighlightCrossRefDao(): SavedItemAndHighlightCrossRefDao } 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 index 535ee1899..d559aff3b 100644 --- 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 @@ -3,6 +3,7 @@ package app.omnivore.omnivore.persistence.entities import androidx.core.net.toUri import androidx.lifecycle.LiveData import androidx.room.* +import app.omnivore.omnivore.models.ServerSyncStatus import java.util.* @Entity @@ -71,6 +72,58 @@ data class TypeaheadCardData( val isArchived: Boolean, ) +@Dao +abstract class SavedItemWithLabelsAndHighlightsDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertSavedItems(items: List) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertLabelCrossRefs(items: List) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertLabels(items: List) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertHighlights(items: List) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + abstract fun insertHighlightCrossRefs(items: List) + + + @Transaction + open fun insertAll(savedItems: List) { + insertSavedItems(savedItems.map { it.savedItem }) + + val labels: MutableList = mutableListOf() + val highlights: MutableList = mutableListOf() + + val labelCrossRefs: MutableList = mutableListOf() + val highlightCrossRefs: MutableList = mutableListOf() + + for (searchItem in savedItems) { + labels.addAll(searchItem.labels) + + val newLabelCrossRefs = searchItem.labels.map { + SavedItemAndSavedItemLabelCrossRef(savedItemLabelId = it.savedItemLabelId, savedItemId = searchItem.savedItem.savedItemId) + } + + val newHighlightCrossRefs = searchItem.highlights.map { + SavedItemAndHighlightCrossRef(highlightId = it.highlightId, savedItemId = searchItem.savedItem.savedItemId) + } + + labelCrossRefs.addAll(newLabelCrossRefs) + highlightCrossRefs.addAll(newHighlightCrossRefs) + } + + insertLabels(labels) + insertLabelCrossRefs(labelCrossRefs) + + insertHighlights(highlights) + insertHighlightCrossRefs(highlightCrossRefs) + } +} + @Dao interface SavedItemDao { @Query("SELECT * FROM savedItem") @@ -85,9 +138,6 @@ interface SavedItemDao { @Query("SELECT * FROM savedItem WHERE slug = :slug") fun getSavedItemWithLabelsAndHighlights(slug: String): SavedItemWithLabelsAndHighlights? - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertAll(items: List) - @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(item: SavedItem) From d399bf15f5509576c44e9f72c66275c168a73b56 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 5 May 2023 17:14:00 +0800 Subject: [PATCH 03/15] Insert saved items with highlights in labels in single transaction, reset search cursor when pulling to refresh or changing search filter --- .../omnivore/dataService/LibrarySync.kt | 92 ++----------------- .../persistence/entities/SavedItem.kt | 3 - .../omnivore/ui/library/LibraryViewModel.kt | 5 +- .../ui/savedItemViews/SavedItemCard.kt | 3 +- 4 files changed, 13 insertions(+), 90 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibrarySync.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibrarySync.kt index 5d43370f1..4181471e3 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibrarySync.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/dataService/LibrarySync.kt @@ -91,70 +91,6 @@ suspend fun DataService.sync(since: String, cursor: String?, limit: Int = 20): S } db.savedItemWithLabelsAndHighlightsDao().insertAll(savedItems) -// -// db.savedItemDao().insertAll(savedItems) -// -// val labels: MutableList = mutableListOf() -// val crossRefs: MutableList = mutableListOf() -// -// // save labels -// for (item in syncResult.items) { -// val itemLabels = (item.labels ?: listOf()).map { -// SavedItemLabel( -// savedItemLabelId = it.labelFields.id, -// name = it.labelFields.name, -// color = it.labelFields.color, -// createdAt = null, -// labelDescription = null -// ) -// } -// -// labels.addAll(itemLabels) -// -// val newCrossRefs = itemLabels.map { -// SavedItemAndSavedItemLabelCrossRef( -// savedItemLabelId = it.savedItemLabelId, -// savedItemId = item.id -// ) -// } -// -// crossRefs.addAll(newCrossRefs) -// } -// -// db.savedItemLabelDao().insertAll(labels) -// db.savedItemAndSavedItemLabelCrossRefDao().insertAll(crossRefs) -// -// // Persist Highlights -// db.highlightDao().insertAll(syncResult.items.flatMap { -// it.highlights ?: listOf() -// }.map { -// Highlight( -// type = it.highlightFields.type.toString(), -// highlightId = it.highlightFields.id, -// annotation = it.highlightFields.annotation, -// createdByMe = it.highlightFields.createdByMe, -// markedForDeletion = false, -// patch = it.highlightFields.patch, -// prefix = it.highlightFields.prefix, -// quote = it.highlightFields.quote, -// serverSyncStatus = ServerSyncStatus.IS_SYNCED.rawValue, -// shortId = it.highlightFields.shortId, -// suffix = it.highlightFields.suffix, -// createdAt = null, -// updatedAt = it.highlightFields.updatedAt as String?, -// ) -// }) -// -// val highlightCrossRefs = syncResult.items.flatMap { -// val savedItem = it -// (savedItem.highlights ?: listOf()).map { -// Pair(it, savedItem.id) -// } -// }.map { -// SavedItemAndHighlightCrossRef(highlightId = it.first.highlightFields.id, savedItemId = it.second) -// } -// -// db.savedItemAndHighlightCrossRefDao().insertAll(highlightCrossRefs) Log.d("sync", "found ${syncResult.items.size} items with sync api. Since: $since") @@ -175,28 +111,16 @@ fun DataService.isSavedItemContentStoredInDB(slug: String): Boolean { suspend fun DataService.fetchSavedItemContent(slug: String) { val syncResult = networker.savedItem(slug) - val isSuccess = syncResult.item != null - val savedItem = syncResult.item ?: return - db.savedItemDao().insert(savedItem) - - // Persist Labels - db.savedItemLabelDao().insertAll(syncResult.labels) - - val labelCrossRefs = syncResult.labels.map { - SavedItemAndSavedItemLabelCrossRef(savedItemLabelId = it.savedItemLabelId, savedItemId = savedItem.savedItemId) + val savedItem = syncResult.item + savedItem?.let { + val item = SavedItemWithLabelsAndHighlights( + savedItem = savedItem, + labels = syncResult.labels, + highlights = syncResult.highlights + ) + db.savedItemWithLabelsAndHighlightsDao().insertAll(listOf(item)) } - - db.savedItemAndSavedItemLabelCrossRefDao().insertAll(labelCrossRefs) - - // Persist Highlights - db.highlightDao().insertAll(syncResult.highlights) - - val highlightCrossRefs = syncResult.highlights.map { - SavedItemAndHighlightCrossRef(highlightId = it.highlightId, savedItemId = savedItem.savedItemId) - } - - db.savedItemAndHighlightCrossRefDao().insertAll(highlightCrossRefs) } 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 index d559aff3b..a44e3e32e 100644 --- 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 @@ -138,9 +138,6 @@ interface SavedItemDao { @Query("SELECT * FROM savedItem WHERE slug = :slug") fun getSavedItemWithLabelsAndHighlights(slug: String): SavedItemWithLabelsAndHighlights? - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insert(item: SavedItem) - @Query("DELETE FROM savedItem WHERE savedItemId = :itemID") fun deleteById(itemID: String) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt index 577526da6..bffdf8923 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt @@ -76,6 +76,8 @@ class LibraryViewModel @Inject constructor( } fun refresh() { + cursor = null + librarySearchCursor = null isRefreshing = true load(true) } @@ -164,8 +166,9 @@ class LibraryViewModel @Inject constructor( } } fun handleFilterChanges() { + librarySearchCursor = null + if (appliedSortFilterLiveData.value != null && appliedFilterLiveData.value != null) { - val applied = appliedFilterLiveData.value val sortKey = when (appliedSortFilterLiveData.value) { SavedItemSortFilter.NEWEST -> "newest" SavedItemSortFilter.OLDEST -> "oldest" diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt index 126ac2570..ee3fe78bc 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemCard.kt @@ -1,6 +1,7 @@ package app.omnivore.omnivore.ui.savedItemViews import LabelChip +import android.util.Log import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow @@ -34,8 +35,6 @@ import coil.compose.rememberAsyncImagePainter fun SavedItemCard(savedItemViewModel: SavedItemViewModel, savedItem: SavedItemWithLabelsAndHighlights, onClickHandler: () -> Unit, actionHandler: (SavedItemAction) -> Unit) { val listState = rememberLazyListState() - val actionsMenuItem: SavedItemWithLabelsAndHighlights? by savedItemViewModel.actionsMenuItemLiveData.observeAsState(null) - Column( modifier = Modifier .combinedClickable( From 34f0a964ec659edd7b8f884f92adef6eb186af0c Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 5 May 2023 17:30:54 +0800 Subject: [PATCH 04/15] Remove unused script handler for background colours This isn't needed anymore as the bundled reader will display the background colour. --- .../java/app/omnivore/omnivore/ui/reader/WebReader.kt | 8 -------- 1 file changed, 8 deletions(-) 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 c4576e921..80d2c5592 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 @@ -115,14 +115,6 @@ fun WebReader( for (script in webReaderViewModel.javascriptDispatchQueue) { Log.d("js", "executing script: $script") it.evaluateJavascript(script, null) - - if (script.contains("event.isDark")) { - if (script.contains("event.isDark = 'true'")) { - it.setBackgroundColor(Color.Transparent.hashCode()) - } else { - it.setBackgroundColor(Color.White.hashCode()) - } - } } webReaderViewModel.resetJavascriptDispatchQueue() } From 135ecbaa11f2ac9a8912eb65e16e35cf21f4c10c Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 5 May 2023 21:45:04 +0800 Subject: [PATCH 05/15] Use a bottom sheet for theme, set header based on theme --- .../ui/reader/WebPreferencesDialog.kt | 322 +++++++++++------- .../ui/reader/WebReaderLoadingContainer.kt | 254 +++++++++----- .../omnivore/ui/reader/WebReaderViewModel.kt | 44 +-- 3 files changed, 368 insertions(+), 252 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebPreferencesDialog.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebPreferencesDialog.kt index 3a967f554..a0b55f298 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebPreferencesDialog.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebPreferencesDialog.kt @@ -1,47 +1,31 @@ package app.omnivore.omnivore.ui.reader import android.util.Log -import androidx.compose.foundation.clickable +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.rememberLazyListState + import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.Switch import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.KeyboardArrowDown -import androidx.compose.material.icons.filled.KeyboardArrowRight -import androidx.compose.material3.Divider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog +import androidx.compose.ui.unit.sp import app.omnivore.omnivore.R import app.omnivore.omnivore.ui.components.SegmentedControl - -@Composable -fun WebPreferencesDialog(onDismiss: () -> Unit, webReaderViewModel: WebReaderViewModel) { - Dialog(onDismissRequest = { onDismiss() }) { - Surface( - shape = RoundedCornerShape(16.dp), - color = Color.White, - modifier = Modifier - .height(350.dp) - ) { - WebPreferencesView(webReaderViewModel) - } - } -} +import app.omnivore.omnivore.ui.theme.OmnivoreTheme @Composable fun WebPreferencesView(webReaderViewModel: WebReaderViewModel) { @@ -54,125 +38,207 @@ fun WebPreferencesView(webReaderViewModel: WebReaderViewModel) { val selectedWebFontRawValue = remember { mutableStateOf(currentWebPreferences.fontFamily.rawValue) } + + var fontSizeSliderValue by remember { mutableStateOf(currentWebPreferences.textFontSize.toFloat()) } + var marginSliderValue by remember { mutableStateOf(currentWebPreferences.maxWidthPercentage.toFloat()) } + var lineSpacingSliderValue by remember { mutableStateOf(currentWebPreferences.lineHeight.toFloat()) } + + val themeState = remember { mutableStateOf(currentWebPreferences.storedThemePreference) } + + val themeListState = rememberLazyListState() + + OmnivoreTheme() { Column( modifier = Modifier - .padding(top = 6.dp, start = 6.dp, end = 6.dp, bottom = 6.dp) + .padding(horizontal = 15.dp) + .verticalScroll(rememberScrollState()) ) { Row( modifier = Modifier .fillMaxWidth() - .padding(top = 12.dp, bottom = 12.dp), - horizontalArrangement = Arrangement.Center + .padding(bottom = 15.dp), + verticalAlignment = Alignment.CenterVertically, ) { - Text("Web Preferences") - } - - Column( - modifier = Modifier - .verticalScroll(rememberScrollState()) - ) { - Stepper( - label = "Font Size:", - onIncrease = { webReaderViewModel.updateFontSize(isIncrease = true) }, - onDecrease = { webReaderViewModel.updateFontSize(isIncrease = false) } - ) - - Stepper( - label = "Margin:", - onIncrease = { webReaderViewModel.updateMaxWidthPercentage(isIncrease = false) }, - onDecrease = { webReaderViewModel.updateMaxWidthPercentage(isIncrease = true) } - ) - - Stepper( - label = "Line Spacing:", - onIncrease = { webReaderViewModel.updateLineSpacing(isIncrease = true) }, - onDecrease = { webReaderViewModel.updateLineSpacing(isIncrease = false) } - ) - - Row(verticalAlignment = Alignment.CenterVertically) { - Text("High Contrast Text") - Spacer(modifier = Modifier.weight(1.0F)) - Switch( - checked = highContrastTextSwitchState.value, - onCheckedChange = { - highContrastTextSwitchState.value = it - webReaderViewModel.updateHighContrastTextPreference(it) - } - ) - } - - Row(verticalAlignment = Alignment.CenterVertically) { - Text("Justify Text") - Spacer(modifier = Modifier.weight(1.0F)) - Switch( - checked = justifyTextSwitchState.value, - onCheckedChange = { - justifyTextSwitchState.value = it - webReaderViewModel.updateJustifyText(it) - } - ) - } - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .padding(vertical = 4.dp) - ) { - Text("Theme:") - Spacer(modifier = Modifier.weight(1.0F)) - SegmentedControl( - items = webReaderViewModel.systemThemeKeys, - initialSelectedItemIndex = webReaderViewModel.systemThemeKeys.indexOf(currentWebPreferences.storedThemePreference) + Text("Font", style = TextStyle( + fontSize = 15.sp, + fontWeight = FontWeight.Normal, + color = Color(red = 137, green = 137, blue = 137) + )) + Spacer(modifier = Modifier.weight(1.0F)) + Box { + OutlinedButton( + shape = RoundedCornerShape(4.dp), + onClick = { isFontListExpanded.value = true }, + colors = ButtonDefaults.buttonColors( + contentColor = Color(red = 137, green = 137, blue = 137), + // containerColor = Color.Transparent, + ), ) { - webReaderViewModel.updateStoredThemePreference(it, isDark) + Text(selectedWebFontRawValue.value) } - } - - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .clickable(onClick = { isFontListExpanded.value = !isFontListExpanded.value }) - ) { - Text("Font Family") - Spacer(modifier = Modifier.weight(1.0F)) - Icon( - imageVector = - if (isFontListExpanded.value) - Icons.Filled.KeyboardArrowDown - else - Icons.Filled.KeyboardArrowRight, - contentDescription = null - ) - } - - if (isFontListExpanded.value) { - WebFont.values().forEach { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .clickable(onClick = { - webReaderViewModel.applyWebFont(it) - selectedWebFontRawValue.value = it.rawValue - }) + if (isFontListExpanded.value) { + DropdownMenu( + expanded = isFontListExpanded.value, + onDismissRequest = { isFontListExpanded.value = false } ) { - Text( - it.displayText, - modifier = Modifier - .padding(top = 6.dp, start = 6.dp, end = 6.dp, bottom = 6.dp) - ) - Spacer(modifier = Modifier.weight(1.0F)) - if (it.rawValue == selectedWebFontRawValue.value) { - Icon( - imageVector = Icons.Filled.Check, - contentDescription = null + WebFont.values().forEach { + DropdownMenuItem( + text = { Text(it.displayText) }, + onClick = { + webReaderViewModel.applyWebFont(it) + selectedWebFontRawValue.value = it.rawValue + isFontListExpanded.value = false + }, + colors = MenuDefaults.itemColors( + textColor = Color(red = 137, green = 137, blue = 137), + ) ) } } } } } + + Text("Font Size:", style = TextStyle( + fontSize = 15.sp, + fontWeight = FontWeight.Normal, + color = Color(red = 137, green = 137, blue = 137) + )) + Slider( + value = fontSizeSliderValue, + onValueChange = { + fontSizeSliderValue = it + webReaderViewModel.setFontSize(it.toInt()) + }, + steps = 10, + valueRange = 8f..28f, + ) + + Text("Margin", style = TextStyle( + fontSize = 15.sp, + fontWeight = FontWeight.Normal, + color = Color(red = 137, green = 137, blue = 137) + )) + Slider( + value = marginSliderValue, + onValueChange = { + marginSliderValue = it + webReaderViewModel.setMaxWidthPercentage(it.toInt()) + }, + steps = 4, + valueRange = 60f..100f, + ) + + Text("Line Spacing", style = TextStyle( + fontSize = 15.sp, + fontWeight = FontWeight.Normal, + color = Color(red = 137, green = 137, blue = 137) + )) + Slider( + value = lineSpacingSliderValue, + onValueChange = { + lineSpacingSliderValue = it + webReaderViewModel.setLineHeight(it.toInt()) + }, + steps = 8, + valueRange = 100f..300f, + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .padding(vertical = 4.dp) + ) { + Text("Theme:", style = TextStyle( + fontSize = 15.sp, + fontWeight = FontWeight.Normal, + color = Color(red = 137, green = 137, blue = 137) + )) + Spacer(modifier = Modifier.weight(1.0F)) + Text("Auto", style = TextStyle( + fontSize = 10.sp, + fontWeight = FontWeight.Normal, + color = Color(red = 137, green = 137, blue = 137) + )) + Checkbox( + checked = themeState.value == "System", + onCheckedChange = { + if (it) { + themeState.value = "System" + webReaderViewModel.updateStoredThemePreference("System", isDark) + } else { + val newThemeKey = if (isDark) "Black" else "Light" + themeState.value = newThemeKey + webReaderViewModel.updateStoredThemePreference(newThemeKey, isDark) + } + }) + } + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.Start, + ) { + for(theme in Themes.values()) { + if (theme.themeKey != "System") { + val isSelected = theme.themeKey == themeState.value + Button( + onClick = { + themeState.value = theme.themeKey + webReaderViewModel.updateStoredThemePreference(theme.themeKey, isDark) + }, + shape = CircleShape, + border = BorderStroke(3.dp, if (isSelected) colorResource(R.color.cta_yellow) else Color.Transparent), + modifier = Modifier.size(35.dp), + colors = ButtonDefaults.buttonColors( + + containerColor = Color(theme.backgroundColor) + ) + ) { + + } + Spacer(modifier = Modifier.weight(0.1F)) + } + + } + Spacer(modifier = Modifier.weight(2.0F)) + } + + Row(verticalAlignment = Alignment.CenterVertically) { + Text("High Contrast Text", + style = TextStyle( + fontSize = 15.sp, + fontWeight = FontWeight.Normal, + color = Color(red = 137, green = 137, blue = 137) + )) + Spacer(modifier = Modifier.weight(1.0F)) + Switch( + checked = highContrastTextSwitchState.value, + onCheckedChange = { + highContrastTextSwitchState.value = it + webReaderViewModel.updateHighContrastTextPreference(it) + } + ) + } + + Row(verticalAlignment = Alignment.CenterVertically) { + Text("Justify Text", + style = TextStyle( + fontSize = 15.sp, + fontWeight = FontWeight.Normal, + color = Color(red = 137, green = 137, blue = 137)) + ) + Spacer(modifier = Modifier.weight(1.0F)) + Switch( + checked = justifyTextSwitchState.value, + onCheckedChange = { + justifyTextSwitchState.value = it + webReaderViewModel.updateJustifyText(it) + } + ) + } } } +} @Composable fun Stepper(label: String, onIncrease: () -> Unit, onDecrease: () -> Unit) { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index 7d64466ce..e08adbea8 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -11,12 +11,14 @@ import androidx.activity.viewModels import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* -import androidx.compose.material.TopAppBar +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.* import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -25,6 +27,7 @@ import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity @@ -42,9 +45,8 @@ import app.omnivore.omnivore.ui.theme.OmnivoreTheme import com.google.accompanist.systemuicontroller.rememberSystemUiController import dagger.hilt.android.AndroidEntryPoint import kotlin.math.roundToInt -import androidx.navigation.compose.rememberNavController -import app.omnivore.omnivore.Routes import app.omnivore.omnivore.ui.notebook.NotebookActivity +import kotlinx.coroutines.launch @AndroidEntryPoint @@ -105,6 +107,7 @@ class WebReaderLoadingContainerActivity: ComponentActivity() { } } +@OptIn(ExperimentalMaterialApi::class) @Composable fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, onLibraryIconTap: (() -> Unit)? = null, webReaderViewModel: WebReaderViewModel) { val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher @@ -112,6 +115,10 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o var isMenuExpanded by remember { mutableStateOf(false) } var showWebPreferencesDialog by remember { mutableStateOf(false ) } + val isDark = isSystemInDarkTheme() + val currentWebPreferences = webReaderViewModel.storedWebPreferences(isDark) + val currentTheme = Themes.values().find { it.themeKey == currentWebPreferences.themeKey } + val webReaderParams: WebReaderParams? by webReaderViewModel.webReaderParamsLiveData.observeAsState(null) val annotation: String? by webReaderViewModel.annotationLiveData.observeAsState(null) val shouldPopView: Boolean by webReaderViewModel.shouldPopViewLiveData.observeAsState(false) @@ -123,6 +130,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o webReaderViewModel.loadItem(slug = slug, requestID = requestID) val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() val styledContent = webReaderParams?.let { val webReaderContent = WebReaderContent( @@ -133,111 +141,169 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o webReaderContent.styledContent() } ?: null - Box( - modifier = Modifier - .fillMaxSize() - .systemBarsPadding() - .background(color = backgroundColor) - ) { - if (styledContent != null) { - WebReader( - preferences = webReaderViewModel.storedWebPreferences(isSystemInDarkTheme()), - styledContent = styledContent, - webReaderViewModel = webReaderViewModel - ) + val modalBottomSheetState = rememberModalBottomSheetState( + ModalBottomSheetValue.Hidden, + ) - TopAppBar( - modifier = Modifier - .height(height = with(LocalDensity.current) { - toolbarHeightPx.roundToInt().toDp() - }), - backgroundColor = MaterialTheme.colorScheme.surfaceVariant, - title = {}, - navigationIcon = { - IconButton(onClick = { - onBackPressedDispatcher?.onBackPressed() - }) { - Icon( - imageVector = androidx.compose.material.icons.Icons.Filled.ArrowBack, - modifier = Modifier, - contentDescription = "Back" - ) - } - }, - actions = { - if (onLibraryIconTap != null) { - IconButton(onClick = { onLibraryIconTap() }) { - Icon( - imageVector = Icons.Default.Home, - contentDescription = null - ) - } - } - webReaderParams?.let { + ModalBottomSheetLayout( + modifier = Modifier.statusBarsPadding(), + sheetBackgroundColor = Color.Transparent, + sheetState = modalBottomSheetState, + sheetContent = { + if (showWebPreferencesDialog) { + BottomSheetUI("Reader Preferences") { + WebPreferencesView(webReaderViewModel) + } + } + Spacer(modifier = Modifier.weight(1.0F)) + } + ) { + Box( + modifier = Modifier + .fillMaxSize() + .systemBarsPadding() + .background(color = backgroundColor) + ) { + if (styledContent != null) { + WebReader( + preferences = webReaderViewModel.storedWebPreferences(isSystemInDarkTheme()), + styledContent = styledContent, + webReaderViewModel = webReaderViewModel + ) + + TopAppBar( + modifier = Modifier + .height(height = with(LocalDensity.current) { + toolbarHeightPx.roundToInt().toDp() + }), + backgroundColor = Color(currentTheme?.backgroundColor ?: 0xFFFFFFFF), // MaterialTheme.colorScheme.surfaceVariant, + title = {}, + navigationIcon = { IconButton(onClick = { - val intent = Intent(context, NotebookActivity::class.java) - intent.putExtra("SAVED_ITEM_ID", it.item.savedItemId) - context.startActivity(intent) + onBackPressedDispatcher?.onBackPressed() }) { Icon( - painter = painterResource(id = R.drawable.notebook), + imageVector = Icons.Filled.ArrowBack, + modifier = Modifier, + contentDescription = "Back" + ) + } + }, + actions = { + if (onLibraryIconTap != null) { + IconButton(onClick = { onLibraryIconTap() }) { + Icon( + imageVector = Icons.Default.Home, + contentDescription = null + ) + } + } + webReaderParams?.let { + IconButton(onClick = { + val intent = Intent(context, NotebookActivity::class.java) + intent.putExtra("SAVED_ITEM_ID", it.item.savedItemId) + context.startActivity(intent) + }) { + Icon( + painter = painterResource(id = R.drawable.notebook), + contentDescription = null + ) + } + } + IconButton(onClick = { + showWebPreferencesDialog = true + coroutineScope.launch { + modalBottomSheetState.show() + } + }) { + Icon( + painter = painterResource(id = R.drawable.format_letter_case), contentDescription = null ) } - } - IconButton(onClick = { showWebPreferencesDialog = true }) { - Icon( - painter = painterResource(id = R.drawable.format_letter_case), - contentDescription = null - ) - } - IconButton(onClick = { isMenuExpanded = true }) { - Icon( - painter = painterResource(id = R.drawable.dots_horizontal), - contentDescription = null - ) - } - SavedItemContextMenu( - isExpanded = isMenuExpanded, - isArchived = webReaderParams!!.item.isArchived, - onDismiss = { isMenuExpanded = false }, - actionHandler = { - webReaderViewModel.handleSavedItemAction( - webReaderParams!!.item.savedItemId, - it + IconButton(onClick = { isMenuExpanded = true }) { + Icon( + painter = painterResource(id = R.drawable.dots_horizontal), + contentDescription = null ) } + SavedItemContextMenu( + isExpanded = isMenuExpanded, + isArchived = webReaderParams!!.item.isArchived, + onDismiss = { isMenuExpanded = false }, + actionHandler = { + webReaderViewModel.handleSavedItemAction( + webReaderParams!!.item.savedItemId, + it + ) + } + ) + } + ) + + if (annotation != null) { + AnnotationEditView( + initialAnnotation = annotation!!, + onSave = { + webReaderViewModel.saveAnnotation(it) + }, + onCancel = { + webReaderViewModel.cancelAnnotationEdit() + } ) } - ) - if (showWebPreferencesDialog) { - WebPreferencesDialog( - onDismiss = { - showWebPreferencesDialog = false - }, - webReaderViewModel = webReaderViewModel - ) + WebReaderLabelsSelectionSheet(webReaderViewModel) } - if (annotation != null) { - AnnotationEditView( - initialAnnotation = annotation!!, - onSave = { - webReaderViewModel.saveAnnotation(it) - }, - onCancel = { - webReaderViewModel.cancelAnnotationEdit() - } - ) - } - - WebReaderLabelsSelectionSheet(webReaderViewModel) - } - - LaunchedEffect(shouldPopView) { - if (shouldPopView) { - onBackPressedDispatcher?.onBackPressed() + LaunchedEffect(shouldPopView) { + if (shouldPopView) { + onBackPressedDispatcher?.onBackPressed() + } + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) +@Composable +fun BottomSheetUI(title: String?, content: @Composable () -> Unit) { + val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher + + Box( + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth() + .clip(RoundedCornerShape(topEnd = 20.dp, topStart = 20.dp)) + .background(Color.White) + .statusBarsPadding() + ) { + Scaffold( + topBar = { + TopAppBar( + title = { Text(title ?: "") }, + modifier = Modifier.statusBarsPadding(), + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.background + ), +// navigationIcon = { +// IconButton(onClick = { +// onBackPressedDispatcher?.onBackPressed() +// }) { +// Icon( +// imageVector = Icons.Filled.ArrowBack, +// modifier = Modifier, +// contentDescription = "Back" +// ) +// } +// } + ) + } + ) { paddingValues -> + Box(modifier = Modifier + .padding(paddingValues) + .fillMaxSize()) { + content() } } } 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 9689656fc..ec6470a39 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 @@ -37,6 +37,15 @@ data class AnnotationWebViewMessage( val annotation: String? ) +enum class Themes(val themeKey: String, val backgroundColor: Long) { + SYSTEM("System", 0xFFFFFFFF), + LIGHT("Light", 0xFF000000), + SEPIA("Sepia", 0xFFFBF0D9), + DARK("Dark", 0xFF2a2a2a), + APOLLO("Apollo", 0xFF6A6968), + BLACK("Black", 0xFFFFFFFF), +} + @HiltViewModel class WebReaderViewModel @Inject constructor( private val datastoreRepo: DatastoreRepository, @@ -56,9 +65,6 @@ class WebReaderViewModel @Inject constructor( val showLabelsSelectionSheetLiveData = MutableLiveData(false) val savedItemLabelsLiveData = dataService.db.savedItemLabelDao().getSavedItemLabelsLiveData() - // "Sepia", "Apollo", - val systemThemeKeys = listOf("Light", "Black", "System") - var hasTappedExistingHighlight = false var lastTapCoordinates: TapCoordinates? = null private var isLoading = false @@ -288,59 +294,37 @@ class WebReaderViewModel @Inject constructor( return storedThemePreference } - fun updateStoredThemePreference(index: Int, isDarkMode: Boolean) { - val newThemeKey = themeKey(isDarkMode, systemThemeKeys[index]) + fun updateStoredThemePreference(newThemeKey: String, isDarkMode: Boolean) { Log.d("theme", "Setting theme key: ${newThemeKey}") runBlocking { - datastoreRepo.putString(DatastoreKeys.preferredTheme, systemThemeKeys[index]) + datastoreRepo.putString(DatastoreKeys.preferredTheme, newThemeKey) } val script = "var event = new Event('updateTheme');event.themeName = '$newThemeKey';document.dispatchEvent(event);" enqueueScript(script) } - fun updateFontSize(isIncrease: Boolean) { - val delta = if (isIncrease) 2 else -2 - var newFontSize: Int - + fun setFontSize(newFontSize: Int) { runBlocking { - val storedFontSize = datastoreRepo.getInt(DatastoreKeys.preferredWebFontSize) - newFontSize = ((storedFontSize ?: 12) + delta).coerceIn(8, 28) datastoreRepo.putInt(DatastoreKeys.preferredWebFontSize, newFontSize) } - - // Get value from data store and then update it val script = "var event = new Event('updateFontSize');event.fontSize = '$newFontSize';document.dispatchEvent(event);" enqueueScript(script) } - fun updateMaxWidthPercentage(isIncrease: Boolean) { - val delta = if (isIncrease) 10 else -10 - var newMaxWidthPercentageValue: Int - + fun setMaxWidthPercentage(newMaxWidthPercentageValue: Int) { runBlocking { - val storedWidth = datastoreRepo.getInt(DatastoreKeys.preferredWebMaxWidthPercentage) - newMaxWidthPercentageValue = ((storedWidth ?: 100) + delta).coerceIn(40, 100) datastoreRepo.putInt(DatastoreKeys.preferredWebMaxWidthPercentage, newMaxWidthPercentageValue) } - - // Get value from data store and then update it val script = "var event = new Event('updateMaxWidthPercentage');event.maxWidthPercentage = '$newMaxWidthPercentageValue';document.dispatchEvent(event);" enqueueScript(script) } - fun updateLineSpacing(isIncrease: Boolean) { - val delta = if (isIncrease) 25 else -25 - var newLineHeight: Int - + fun setLineHeight(newLineHeight: Int) { runBlocking { - val storedHeight = datastoreRepo.getInt(DatastoreKeys.preferredWebLineHeight) - newLineHeight = ((storedHeight ?: 150) + delta).coerceIn(100, 300) datastoreRepo.putInt(DatastoreKeys.preferredWebLineHeight, newLineHeight) } - - // Get value from data store and then update it val script = "var event = new Event('updateLineHeight');event.lineHeight = '$newLineHeight';document.dispatchEvent(event);" enqueueScript(script) } From bf4b3d5b79e3058b15099558598b75f980ad20e7 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Sat, 6 May 2023 00:06:24 +0800 Subject: [PATCH 06/15] Add all highlights before inserting cross refs --- android/Omnivore/app/build.gradle | 4 ++-- .../app/omnivore/omnivore/persistence/entities/Highlight.kt | 1 - .../app/omnivore/omnivore/persistence/entities/SavedItem.kt | 6 ++++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index c20cfc63f..70219ca08 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "app.omnivore.omnivore" minSdk 26 targetSdk 33 - versionCode 61 - versionName "0.0.61" + versionCode 66 + versionName "0.0.66" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { 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 index 0071697c6..a54bc679b 100644 --- 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 @@ -34,7 +34,6 @@ data class Highlight( entity = Highlight::class, parentColumns = arrayOf("highlightId"), childColumns = arrayOf("highlightId"), - onDelete = ForeignKey.CASCADE ), ForeignKey( entity = SavedItem::class, 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 index a44e3e32e..e67b14357 100644 --- 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 @@ -1,5 +1,6 @@ package app.omnivore.omnivore.persistence.entities +import android.util.Log import androidx.core.net.toUri import androidx.lifecycle.LiveData import androidx.room.* @@ -90,7 +91,6 @@ abstract class SavedItemWithLabelsAndHighlightsDao { @Insert(onConflict = OnConflictStrategy.REPLACE) abstract fun insertHighlightCrossRefs(items: List) - @Transaction open fun insertAll(savedItems: List) { insertSavedItems(savedItems.map { it.savedItem }) @@ -103,6 +103,7 @@ abstract class SavedItemWithLabelsAndHighlightsDao { for (searchItem in savedItems) { labels.addAll(searchItem.labels) + highlights.addAll(searchItem.highlights) val newLabelCrossRefs = searchItem.labels.map { SavedItemAndSavedItemLabelCrossRef(savedItemLabelId = it.savedItemLabelId, savedItemId = searchItem.savedItem.savedItemId) @@ -190,7 +191,7 @@ interface SavedItemDao { fun _filteredLibraryData(allowedArchiveStates: List, sortKey: String, hasRequiredLabels: Int, hasExcludedLabels: Int, requiredLabels: List, excludedLabels: List, allowedContentReaders: List): LiveData> fun filteredLibraryData(allowedArchiveStates: List, sortKey: String, requiredLabels: List, excludedLabels: List, allowedContentReaders: List): LiveData> { - return _filteredLibraryData( + val result = _filteredLibraryData( allowedArchiveStates = allowedArchiveStates, sortKey = sortKey, hasRequiredLabels = requiredLabels.size, @@ -199,6 +200,7 @@ interface SavedItemDao { excludedLabels = excludedLabels, allowedContentReaders = allowedContentReaders ) + return result } } From 6a99bc5ed615bb28a365e08f9be067537a974a70 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Sat, 6 May 2023 00:07:02 +0800 Subject: [PATCH 07/15] Swap black and white --- .../java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ec6470a39..4a89e4c76 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 @@ -43,7 +43,7 @@ enum class Themes(val themeKey: String, val backgroundColor: Long) { SEPIA("Sepia", 0xFFFBF0D9), DARK("Dark", 0xFF2a2a2a), APOLLO("Apollo", 0xFF6A6968), - BLACK("Black", 0xFFFFFFFF), + BLACK("Black", 0xFF000000), } @HiltViewModel From 44309467da1539e5312dedb8a03a5ea98fcff834 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 May 2023 13:52:03 +0800 Subject: [PATCH 08/15] Update navbar colours from themekey live data --- android/Omnivore/app/build.gradle | 4 +- .../omnivore/omnivore/DatastoreRepository.kt | 8 ++ .../ui/reader/WebReaderLoadingContainer.kt | 115 +++++++++--------- .../omnivore/ui/reader/WebReaderViewModel.kt | 24 ++-- 4 files changed, 83 insertions(+), 68 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index 70219ca08..7efadfe3b 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "app.omnivore.omnivore" minSdk 26 targetSdk 33 - versionCode 66 - versionName "0.0.66" + versionCode 67 + versionName "0.0.67" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/DatastoreRepository.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/DatastoreRepository.kt index 725054822..9fa050fa4 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/DatastoreRepository.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/DatastoreRepository.kt @@ -11,6 +11,8 @@ import javax.inject.Inject interface DatastoreRepository { val hasAuthTokenFlow: Flow + val themeKeyFlow: Flow + suspend fun clear() suspend fun putString(key: String, value: String) suspend fun putInt(key: String, value: Int) @@ -67,4 +69,10 @@ class OmnivoreDatastore @Inject constructor( val token = preferences[key] token != null } + + override val themeKeyFlow: Flow = context + .dataStore.data.map { preferences -> + val key = stringPreferencesKey(DatastoreKeys.preferredTheme) + preferences[key] ?: "System" + } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index e08adbea8..c248ee930 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -1,8 +1,8 @@ package app.omnivore.omnivore.ui.reader +import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle -import android.util.Log import android.view.View import androidx.activity.ComponentActivity import androidx.activity.compose.LocalOnBackPressedDispatcherOwner @@ -16,8 +16,6 @@ import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.Menu -import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.* import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -25,7 +23,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color @@ -36,17 +33,16 @@ import androidx.compose.ui.unit.dp import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat -import androidx.lifecycle.viewmodel.compose.viewModel import app.omnivore.omnivore.MainActivity import app.omnivore.omnivore.R import app.omnivore.omnivore.ui.components.WebReaderLabelsSelectionSheet +import app.omnivore.omnivore.ui.notebook.NotebookActivity import app.omnivore.omnivore.ui.savedItemViews.SavedItemContextMenu import app.omnivore.omnivore.ui.theme.OmnivoreTheme import com.google.accompanist.systemuicontroller.rememberSystemUiController import dagger.hilt.android.AndroidEntryPoint -import kotlin.math.roundToInt -import app.omnivore.omnivore.ui.notebook.NotebookActivity import kotlinx.coroutines.launch +import kotlin.math.roundToInt @AndroidEntryPoint @@ -107,7 +103,7 @@ class WebReaderLoadingContainerActivity: ComponentActivity() { } } -@OptIn(ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Composable fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, onLibraryIconTap: (() -> Unit)? = null, webReaderViewModel: WebReaderViewModel) { val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher @@ -115,9 +111,8 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o var isMenuExpanded by remember { mutableStateOf(false) } var showWebPreferencesDialog by remember { mutableStateOf(false ) } - val isDark = isSystemInDarkTheme() - val currentWebPreferences = webReaderViewModel.storedWebPreferences(isDark) - val currentTheme = Themes.values().find { it.themeKey == currentWebPreferences.themeKey } + val currentThemeKey = webReaderViewModel.currentThemeKey.observeAsState() + val currentTheme = Themes.values().find { it.themeKey == currentThemeKey.value } val webReaderParams: WebReaderParams? by webReaderViewModel.webReaderParamsLiveData.observeAsState(null) val annotation: String? by webReaderViewModel.annotationLiveData.observeAsState(null) @@ -125,7 +120,6 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o val toolbarHeightPx: Float by webReaderViewModel.currentToolbarHeightLiveData.observeAsState(0.0f) val maxToolbarHeight = 48.dp - val backgroundColor = if (isSystemInDarkTheme()) Color.Black else Color.White webReaderViewModel.maxToolbarHeightPx = with(LocalDensity.current) { maxToolbarHeight.roundToPx().toFloat() } webReaderViewModel.loadItem(slug = slug, requestID = requestID) @@ -145,6 +139,8 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o ModalBottomSheetValue.Hidden, ) + val themeTintColor = Color(currentTheme?.foregroundColor ?: 0xFFFFFFFF) + ModalBottomSheetLayout( modifier = Modifier.statusBarsPadding(), sheetBackgroundColor = Color.Transparent, @@ -158,25 +154,15 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o Spacer(modifier = Modifier.weight(1.0F)) } ) { - Box( - modifier = Modifier - .fillMaxSize() - .systemBarsPadding() - .background(color = backgroundColor) - ) { - if (styledContent != null) { - WebReader( - preferences = webReaderViewModel.storedWebPreferences(isSystemInDarkTheme()), - styledContent = styledContent, - webReaderViewModel = webReaderViewModel - ) - - TopAppBar( + Scaffold( + topBar = { + TopAppBar( modifier = Modifier .height(height = with(LocalDensity.current) { toolbarHeightPx.roundToInt().toDp() }), - backgroundColor = Color(currentTheme?.backgroundColor ?: 0xFFFFFFFF), // MaterialTheme.colorScheme.surfaceVariant, + backgroundColor = Color(currentTheme?.backgroundColor ?: 0xFFFFFFFF), + elevation = 0.dp, title = {}, navigationIcon = { IconButton(onClick = { @@ -185,7 +171,8 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o Icon( imageVector = Icons.Filled.ArrowBack, modifier = Modifier, - contentDescription = "Back" + contentDescription = "Back", + tint = themeTintColor ) } }, @@ -194,7 +181,8 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o IconButton(onClick = { onLibraryIconTap() }) { Icon( imageVector = Icons.Default.Home, - contentDescription = null + contentDescription = null, + tint = themeTintColor, ) } } @@ -206,7 +194,8 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o }) { Icon( painter = painterResource(id = R.drawable.notebook), - contentDescription = null + contentDescription = null, + tint = themeTintColor ) } } @@ -218,51 +207,65 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o }) { Icon( painter = painterResource(id = R.drawable.format_letter_case), - contentDescription = null + contentDescription = null, + tint = themeTintColor ) } IconButton(onClick = { isMenuExpanded = true }) { Icon( painter = painterResource(id = R.drawable.dots_horizontal), - contentDescription = null + contentDescription = null, + tint = themeTintColor ) + if (isMenuExpanded) { + webReaderParams?.let { params -> + SavedItemContextMenu( + isExpanded = isMenuExpanded, + isArchived = webReaderParams!!.item.isArchived, + onDismiss = { isMenuExpanded = false }, + actionHandler = { + webReaderViewModel.handleSavedItemAction( + params.item.savedItemId, + it + ) + } + ) + } + } } - SavedItemContextMenu( - isExpanded = isMenuExpanded, - isArchived = webReaderParams!!.item.isArchived, - onDismiss = { isMenuExpanded = false }, - actionHandler = { - webReaderViewModel.handleSavedItemAction( - webReaderParams!!.item.savedItemId, - it - ) + }, + ) + } + ) { paddingValues -> + if (styledContent != null) { + WebReader( + preferences = webReaderViewModel.storedWebPreferences(isSystemInDarkTheme()), + styledContent = styledContent, + webReaderViewModel = webReaderViewModel + ) + } + + if (annotation != null) { + AnnotationEditView( + initialAnnotation = annotation!!, + onSave = { + webReaderViewModel.saveAnnotation(it) + }, + onCancel = { + webReaderViewModel.cancelAnnotationEdit() } ) } - ) - if (annotation != null) { - AnnotationEditView( - initialAnnotation = annotation!!, - onSave = { - webReaderViewModel.saveAnnotation(it) - }, - onCancel = { - webReaderViewModel.cancelAnnotationEdit() - } - ) + WebReaderLabelsSelectionSheet(webReaderViewModel) } - WebReaderLabelsSelectionSheet(webReaderViewModel) - } - LaunchedEffect(shouldPopView) { if (shouldPopView) { onBackPressedDispatcher?.onBackPressed() } } } - } } @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) 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 4a89e4c76..752d42018 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 @@ -6,9 +6,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.* import app.omnivore.omnivore.DatastoreKeys import app.omnivore.omnivore.DatastoreRepository import app.omnivore.omnivore.dataService.* @@ -24,6 +22,7 @@ import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull import com.google.gson.Gson import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.* +import kotlinx.coroutines.flow.distinctUntilChanged import java.util.* import javax.inject.Inject @@ -37,13 +36,13 @@ data class AnnotationWebViewMessage( val annotation: String? ) -enum class Themes(val themeKey: String, val backgroundColor: Long) { - SYSTEM("System", 0xFFFFFFFF), - LIGHT("Light", 0xFF000000), - SEPIA("Sepia", 0xFFFBF0D9), - DARK("Dark", 0xFF2a2a2a), - APOLLO("Apollo", 0xFF6A6968), - BLACK("Black", 0xFF000000), +enum class Themes(val themeKey: String, val backgroundColor: Long, val foregroundColor: Long) { + SYSTEM("System", 0xFFFFFFFF, 0xFF000000), + LIGHT("Light", 0xFFFFFFFF, 0xFF000000), + SEPIA("Sepia", 0xFFFBF0D9, 0xFF000000), + DARK("Dark", 0xFF2F3030, 0xFFFFFFFF), + APOLLO("Apollo", 0xFF6A6968, 0xFFFFFFFF), + BLACK("Black", 0xFF000000, 0xFFFFFFFF), } @HiltViewModel @@ -262,6 +261,11 @@ class WebReaderViewModel @Inject constructor( javascriptActionLoopUUIDLiveData.value = UUID.randomUUID() } + val currentThemeKey: LiveData = datastoreRepo + .themeKeyFlow + .distinctUntilChanged() + .asLiveData() + fun storedWebPreferences(isDarkMode: Boolean): WebPreferences = runBlocking { val storedFontSize = datastoreRepo.getInt(DatastoreKeys.preferredWebFontSize) val storedLineHeight = datastoreRepo.getInt(DatastoreKeys.preferredWebLineHeight) From e4e153e01fa9312066283cd00d32fd10d678b9c7 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 May 2023 15:10:21 +0800 Subject: [PATCH 09/15] Display the system default font name correctly, set colour on font menu items --- ...ncesDialog.kt => ReaderPreferencesView.kt} | 23 +++++++++--------- .../ui/reader/WebReaderLoadingContainer.kt | 24 +------------------ 2 files changed, 13 insertions(+), 34 deletions(-) rename android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/{WebPreferencesDialog.kt => ReaderPreferencesView.kt} (93%) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebPreferencesDialog.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt similarity index 93% rename from android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebPreferencesDialog.kt rename to android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt index a0b55f298..6101a6002 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebPreferencesDialog.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt @@ -1,6 +1,5 @@ package app.omnivore.omnivore.ui.reader -import android.util.Log import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* @@ -24,11 +23,10 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import app.omnivore.omnivore.R -import app.omnivore.omnivore.ui.components.SegmentedControl import app.omnivore.omnivore.ui.theme.OmnivoreTheme @Composable -fun WebPreferencesView(webReaderViewModel: WebReaderViewModel) { +fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) { val isDark = isSystemInDarkTheme() val currentWebPreferences = webReaderViewModel.storedWebPreferences(isDark) val isFontListExpanded = remember { mutableStateOf(false) } @@ -36,7 +34,7 @@ fun WebPreferencesView(webReaderViewModel: WebReaderViewModel) { val justifyTextSwitchState = remember { mutableStateOf(currentWebPreferences.prefersJustifyText) } - val selectedWebFontRawValue = remember { mutableStateOf(currentWebPreferences.fontFamily.rawValue) } + val selectedWebFontName = remember { mutableStateOf(currentWebPreferences.fontFamily.displayText) } var fontSizeSliderValue by remember { mutableStateOf(currentWebPreferences.textFontSize.toFloat()) } @@ -74,24 +72,27 @@ fun WebPreferencesView(webReaderViewModel: WebReaderViewModel) { // containerColor = Color.Transparent, ), ) { - Text(selectedWebFontRawValue.value) + Text(selectedWebFontName.value) } if (isFontListExpanded.value) { DropdownMenu( expanded = isFontListExpanded.value, - onDismissRequest = { isFontListExpanded.value = false } + onDismissRequest = { isFontListExpanded.value = false }, ) { WebFont.values().forEach { DropdownMenuItem( - text = { Text(it.displayText) }, + text = { + Text(it.displayText, style = TextStyle( + fontSize = 15.sp, + fontWeight = FontWeight.Normal, + color = Color(red = 137, green = 137, blue = 137) + )) + }, onClick = { webReaderViewModel.applyWebFont(it) - selectedWebFontRawValue.value = it.rawValue + selectedWebFontName.value = it.displayText isFontListExpanded.value = false }, - colors = MenuDefaults.itemColors( - textColor = Color(red = 137, green = 137, blue = 137), - ) ) } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index c248ee930..9005bec2a 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -1,6 +1,5 @@ package app.omnivore.omnivore.ui.reader -import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle import android.view.View @@ -19,7 +18,6 @@ import androidx.compose.material.icons.filled.Home import androidx.compose.material3.* import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState @@ -148,7 +146,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o sheetContent = { if (showWebPreferencesDialog) { BottomSheetUI("Reader Preferences") { - WebPreferencesView(webReaderViewModel) + ReaderPreferencesView(webReaderViewModel) } } Spacer(modifier = Modifier.weight(1.0F)) @@ -282,26 +280,6 @@ fun BottomSheetUI(title: String?, content: @Composable () -> Unit) { .statusBarsPadding() ) { Scaffold( - topBar = { - TopAppBar( - title = { Text(title ?: "") }, - modifier = Modifier.statusBarsPadding(), - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.background - ), -// navigationIcon = { -// IconButton(onClick = { -// onBackPressedDispatcher?.onBackPressed() -// }) { -// Icon( -// imageVector = Icons.Filled.ArrowBack, -// modifier = Modifier, -// contentDescription = "Back" -// ) -// } -// } - ) - } ) { paddingValues -> Box(modifier = Modifier .padding(paddingValues) From dc7c992ed2b2b0c39abeeaf6ff2cfab762ed77f8 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 May 2023 17:06:27 +0800 Subject: [PATCH 10/15] More work on notebook using bottom sheet --- .../persistence/entities/SavedItem.kt | 2 - .../omnivore/ui/notebook/NotebookView.kt | 100 +++++++++++++----- .../ui/reader/WebReaderLoadingContainer.kt | 60 ++++++++--- 3 files changed, 117 insertions(+), 45 deletions(-) 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 index e67b14357..7ffd74805 100644 --- 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 @@ -156,8 +156,6 @@ interface SavedItemDao { "LEFT OUTER JOIN Highlight on highlight.highlightId = SavedItemAndHighlightCrossRef.highlightId " + "WHERE SavedItem.savedItemId = :savedItemId " + - "AND SavedItem.serverSyncStatus != 2 " + - "AND Highlight.serverSyncStatus != 2 " + "GROUP BY SavedItem.savedItemId " ) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt index cb7834c6c..d33f96401 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt @@ -46,7 +46,10 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController import dagger.hilt.android.AndroidEntryPoint import dev.jeziellago.compose.markdowntext.MarkdownText import kotlinx.coroutines.launch - +import android.util.Log +import androidx.compose.ui.platform.LocalDensity +import app.omnivore.omnivore.persistence.entities.Highlight +import kotlin.math.roundToInt @AndroidEntryPoint class NotebookActivity: ComponentActivity() { @@ -102,61 +105,105 @@ class NotebookActivity: ComponentActivity() { } } +fun notebookMD(notes: List, highlights: List): String { + var result = "" + + if (notes.isNotEmpty()) { + result += "## Notes\n" + notes.forEach { + result += it.annotation + "\n" + } + result += "\n" + } + + if (highlights.isNotEmpty()) { + result += "## Highlights\n" + highlights.forEach { + result += "> ${it.quote}\n" + if ((it.annotation?: "").isNotEmpty()) { + result += it.annotation + "\n" + } + } + result += "\n" + } + + return result +} + @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable fun NotebookView(savedItemId: String, viewModel: NotebookViewModel) { - val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher + var isMenuOpen by remember { + mutableStateOf(false) + } val savedItem = viewModel.getLibraryItemById(savedItemId).observeAsState() val scrollState = rememberScrollState() val modalBottomSheetState = rememberModalBottomSheetState( ModalBottomSheetValue.Hidden, ) + val coroutineScope = rememberCoroutineScope() + val snackBarHostState = remember { SnackbarHostState() } + val clipboard: ClipboardManager? = + LocalContext.current.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager? + val notes = savedItem.value?.highlights?.filter { it.type == "NOTE" } ?: listOf() val highlights = savedItem.value?.highlights?.filter { it.type == "HIGHLIGHT" } ?: listOf() ModalBottomSheetLayout( - modifier = Modifier.statusBarsPadding(), sheetBackgroundColor = Color.Transparent, sheetState = modalBottomSheetState, sheetContent = { - EditNoteModal() + // EditNoteModal() + Spacer(modifier = Modifier.weight(1.0F)) } ) { Scaffold( topBar = { TopAppBar( title = { Text("Notebook") }, - modifier = Modifier.statusBarsPadding(), colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.background ), - navigationIcon = { - IconButton(onClick = { - onBackPressedDispatcher?.onBackPressed() - }) { - Icon( - imageVector = androidx.compose.material.icons.Icons.Filled.ArrowBack, - modifier = Modifier, - contentDescription = "Back" - ) + actions = { + Box { + IconButton(onClick = { + isMenuOpen = true + }) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = null + ) + } + if (isMenuOpen) { + DropdownMenu( + expanded = isMenuOpen, + onDismissRequest = { isMenuOpen = false } + ) { + DropdownMenuItem( + text = { Text("Copy") }, + onClick = { + val clip = ClipData.newPlainText("notebook", notebookMD(notes, highlights)) + clipboard?.let { + it + clipboard?.setPrimaryClip(clip) + } ?: run { + coroutineScope.launch { + snackBarHostState + .showSnackbar("Notebook copied") + } + } + isMenuOpen = false + } + ) + } + } } - }, -// actions = { -// IconButton(onClick = { -// -// }) { -// Icon( -// imageVector = Icons.Default.MoreVert, -// contentDescription = null -// ) -// } -// } + } ) } ) { paddingValues -> Column( modifier = Modifier - .padding(paddingValues) .verticalScroll(scrollState) .fillMaxSize() ) { @@ -166,7 +213,6 @@ fun NotebookView(savedItemId: String, viewModel: NotebookViewModel) { } HighlightsList(it) } - Spacer(Modifier.weight(100f)) } } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index 9005bec2a..8410aeb59 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -24,7 +24,6 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp @@ -34,7 +33,8 @@ import androidx.core.view.WindowInsetsCompat import app.omnivore.omnivore.MainActivity import app.omnivore.omnivore.R import app.omnivore.omnivore.ui.components.WebReaderLabelsSelectionSheet -import app.omnivore.omnivore.ui.notebook.NotebookActivity +import app.omnivore.omnivore.ui.notebook.NotebookView +import app.omnivore.omnivore.ui.notebook.NotebookViewModel import app.omnivore.omnivore.ui.savedItemViews.SavedItemContextMenu import app.omnivore.omnivore.ui.theme.OmnivoreTheme import com.google.accompanist.systemuicontroller.rememberSystemUiController @@ -46,6 +46,7 @@ import kotlin.math.roundToInt @AndroidEntryPoint class WebReaderLoadingContainerActivity: ComponentActivity() { val viewModel: WebReaderViewModel by viewModels() + val notebookViewModel: NotebookViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -79,6 +80,7 @@ class WebReaderLoadingContainerActivity: ComponentActivity() { slug = slug, onLibraryIconTap = if (requestID != null) { { startMainActivity() } } else null, webReaderViewModel = viewModel, + notebookViewModel = notebookViewModel, ) } } @@ -101,13 +103,25 @@ class WebReaderLoadingContainerActivity: ComponentActivity() { } } +enum class BottomSheetState( +) { + NONE(), + PREFERENCES(), + NOTEBOOK(), + HIGHLIGHTNOTE() +} + + @OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Composable -fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, onLibraryIconTap: (() -> Unit)? = null, webReaderViewModel: WebReaderViewModel) { +fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, + onLibraryIconTap: (() -> Unit)? = null, + webReaderViewModel: WebReaderViewModel, + notebookViewModel: NotebookViewModel) { val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher var isMenuExpanded by remember { mutableStateOf(false) } - var showWebPreferencesDialog by remember { mutableStateOf(false ) } + var bottomSheetState by remember { mutableStateOf(app.omnivore.omnivore.ui.reader.BottomSheetState.NONE)} val currentThemeKey = webReaderViewModel.currentThemeKey.observeAsState() val currentTheme = Themes.values().find { it.themeKey == currentThemeKey.value } @@ -121,7 +135,6 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o webReaderViewModel.maxToolbarHeightPx = with(LocalDensity.current) { maxToolbarHeight.roundToPx().toFloat() } webReaderViewModel.loadItem(slug = slug, requestID = requestID) - val context = LocalContext.current val coroutineScope = rememberCoroutineScope() val styledContent = webReaderParams?.let { @@ -140,13 +153,29 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o val themeTintColor = Color(currentTheme?.foregroundColor ?: 0xFFFFFFFF) ModalBottomSheetLayout( - modifier = Modifier.statusBarsPadding(), + modifier = Modifier + .statusBarsPadding(), sheetBackgroundColor = Color.Transparent, sheetState = modalBottomSheetState, sheetContent = { - if (showWebPreferencesDialog) { - BottomSheetUI("Reader Preferences") { - ReaderPreferencesView(webReaderViewModel) + when (bottomSheetState) { + BottomSheetState.PREFERENCES -> { + BottomSheetUI("Reader Preferences") { + ReaderPreferencesView(webReaderViewModel) + } + } + BottomSheetState.NOTEBOOK -> { + webReaderParams?.let { params -> + BottomSheetUI(title = "Notebook") { + NotebookView(savedItemId = params.item.savedItemId, viewModel = notebookViewModel) + } + } + } + BottomSheetState.HIGHLIGHTNOTE -> { + + } + BottomSheetState.NONE -> { + } } Spacer(modifier = Modifier.weight(1.0F)) @@ -186,9 +215,10 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o } webReaderParams?.let { IconButton(onClick = { - val intent = Intent(context, NotebookActivity::class.java) - intent.putExtra("SAVED_ITEM_ID", it.item.savedItemId) - context.startActivity(intent) + bottomSheetState = BottomSheetState.NOTEBOOK + coroutineScope.launch { + modalBottomSheetState.show() + } }) { Icon( painter = painterResource(id = R.drawable.notebook), @@ -198,7 +228,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o } } IconButton(onClick = { - showWebPreferencesDialog = true + bottomSheetState = BottomSheetState.PREFERENCES coroutineScope.launch { modalBottomSheetState.show() } @@ -219,7 +249,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o webReaderParams?.let { params -> SavedItemContextMenu( isExpanded = isMenuExpanded, - isArchived = webReaderParams!!.item.isArchived, + isArchived = params.item.isArchived, onDismiss = { isMenuExpanded = false }, actionHandler = { webReaderViewModel.handleSavedItemAction( @@ -269,8 +299,6 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable fun BottomSheetUI(title: String?, content: @Composable () -> Unit) { - val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher - Box( modifier = Modifier .wrapContentHeight() From 0091d5c42a916aa9d2068b6eb379e6d5363db0c3 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 May 2023 22:24:03 +0800 Subject: [PATCH 11/15] More work on notebook bottom views --- .../ui/components/LabelsSelectionSheet.kt | 1 - .../omnivore/ui/notebook/NotebookView.kt | 64 --------- .../omnivore/ui/reader/AnnotationEditView.kt | 136 ++++++++++++------ .../ui/reader/ReaderPreferencesView.kt | 1 + .../omnivore/omnivore/ui/reader/WebReader.kt | 1 - .../ui/reader/WebReaderLoadingContainer.kt | 96 +++++++------ 6 files changed, 145 insertions(+), 154 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt index ee244b9f8..6adeeb60b 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/components/LabelsSelectionSheet.kt @@ -42,7 +42,6 @@ fun WebReaderLabelsSelectionSheet(viewModel: WebReaderViewModel) { val modalBottomSheetState = rememberModalBottomSheetState( ModalBottomSheetValue.HalfExpanded, - confirmStateChange = { it != ModalBottomSheetValue.Hidden } ) if (isActive) { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt index d33f96401..c0603ef7a 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt @@ -3,12 +3,7 @@ package app.omnivore.omnivore.ui.notebook import android.content.ClipData import android.content.ClipboardManager import android.content.Context -import android.content.Intent -import android.os.Bundle -import androidx.activity.ComponentActivity import androidx.activity.compose.LocalOnBackPressedDispatcherOwner -import androidx.activity.compose.setContent -import androidx.activity.viewModels import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -37,73 +32,14 @@ import androidx.compose.ui.res.colorResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import app.omnivore.omnivore.MainActivity import app.omnivore.omnivore.R import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights import app.omnivore.omnivore.ui.library.* -import app.omnivore.omnivore.ui.theme.OmnivoreTheme -import com.google.accompanist.systemuicontroller.rememberSystemUiController -import dagger.hilt.android.AndroidEntryPoint import dev.jeziellago.compose.markdowntext.MarkdownText import kotlinx.coroutines.launch -import android.util.Log -import androidx.compose.ui.platform.LocalDensity import app.omnivore.omnivore.persistence.entities.Highlight -import kotlin.math.roundToInt -@AndroidEntryPoint -class NotebookActivity: ComponentActivity() { - val viewModel: NotebookViewModel by viewModels() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val savedItemId = intent.getStringExtra("SAVED_ITEM_ID") - - setContent { - val systemUiController = rememberSystemUiController() - val useDarkIcons = !isSystemInDarkTheme() - - DisposableEffect(systemUiController, useDarkIcons) { - systemUiController.setSystemBarsColor( - color = Color.Black, - darkIcons = false - ) - - onDispose {} - } - - OmnivoreTheme { - Box( - modifier = Modifier - .fillMaxSize() - // .background(color = Color.Black) - ) { - savedItemId?.let { - NotebookView( - savedItemId = savedItemId, - viewModel = viewModel - ) - } - } - } - } - } - -// // animate the view up when keyboard appears -// WindowCompat.setDecorFitsSystemWindows(window, false) -// val rootView = findViewById(android.R.id.content).rootView -// ViewCompat.setOnApplyWindowInsetsListener(rootView) { _, insets -> -// val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom -// rootView.setPadding(0, 0, 0, imeHeight) -// insets -// } -// } - - private fun startMainActivity() { - val intent = Intent(this, MainActivity::class.java) - this.startActivity(intent) - } -} fun notebookMD(notes: List, highlights: List): String { var result = "" diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt index 3bd3a875d..f7c1605cb 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/AnnotationEditView.kt @@ -1,5 +1,6 @@ package app.omnivore.omnivore.ui.reader +import android.content.ClipData import android.content.DialogInterface import android.os.Bundle import android.view.LayoutInflater @@ -8,6 +9,10 @@ import android.view.ViewGroup import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -22,7 +27,11 @@ import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.unit.dp import androidx.fragment.app.DialogFragment +import app.omnivore.omnivore.ui.notebook.ArticleNotes +import app.omnivore.omnivore.ui.notebook.HighlightsList +import app.omnivore.omnivore.ui.notebook.notebookMD import app.omnivore.omnivore.ui.theme.OmnivoreTheme +import kotlinx.coroutines.launch class AnnotationEditFragment : DialogFragment() { private var onSave: (String) -> Unit = {} @@ -54,7 +63,7 @@ class AnnotationEditFragment : DialogFragment() { initialAnnotation, onSave, onCancel, - dismissAction = { dismiss() } + // dismissAction = { dismiss() } ) } } @@ -73,62 +82,96 @@ fun AnnotationEditView( initialAnnotation: String, onSave: (String) -> Unit, onCancel: () -> Unit, - dismissAction: () -> Unit = {} ) { val annotation = remember { mutableStateOf(initialAnnotation) } val focusRequester = FocusRequester() - Column( - modifier = Modifier - .clip(RoundedCornerShape(4.dp)) - .background(MaterialTheme.colorScheme.background) - .padding(8.dp), - ) { - Column( - modifier = Modifier.padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Row { - TextButton( - onClick = { + Scaffold( + topBar = { + TopAppBar( + title = { Text("Notebook") }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.background + ), + navigationIcon = { + IconButton(onClick = { onCancel() - dismissAction() + }) { + Icon( + imageVector = Icons.Filled.ArrowBack, + modifier = Modifier, + contentDescription = "Back", + ) } - ) { - Text("Cancel") - } - - Spacer(modifier = Modifier.weight(1.0F)) - - Text(text = "Note") - - Spacer(modifier = Modifier.weight(1.0F)) - - TextButton( - onClick = { - onSave(annotation.value) - dismissAction() + }, + actions = { + TextButton( + onClick = { + onSave(annotation.value) + } + ) { + Text("Save") } - ) { - Text("Save") } - } - - Spacer(modifier = Modifier.height(8.dp)) - + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + ) { TextField( value = annotation.value, onValueChange = { annotation.value = it }, modifier = Modifier - .width(IntrinsicSize.Max) - .height(IntrinsicSize.Max) - .weight(1.0F) .focusRequester(focusRequester) + .fillMaxSize() ) + } + } +} +// +// Column( +// modifier = Modifier.padding(16.dp), +// horizontalAlignment = Alignment.CenterHorizontally, +// ) { +// Row { +// TextButton( +// onClick = { +// onCancel() +// dismissAction() +// } +// ) { +// Text("Cancel") +// } +// +// Spacer(modifier = Modifier.weight(1.0F)) +// +// Text(text = "Note") +// +// Spacer(modifier = Modifier.weight(1.0F)) +// +// TextButton( +// onClick = { +// onSave(annotation.value) +// dismissAction() +// } +// ) { +// Text("Save") +// } +// } +// +// Spacer(modifier = Modifier.height(8.dp)) +// +// +// +// Spacer(modifier = Modifier.height(16.dp)) - LaunchedEffect(Unit) { - focusRequester.requestFocus() - } +// +// LaunchedEffect(Unit) { +// focusRequester.requestFocus() +// } // Row { // Spacer(modifier = Modifier.weight(0.1F)) @@ -142,8 +185,7 @@ fun AnnotationEditView( // ) // Spacer(modifier = Modifier.weight(0.1F)) // } - } - - Spacer(modifier = Modifier.height(16.dp)) - } -} +// } +// +// // } +//} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt index 6101a6002..7d452170b 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/ReaderPreferencesView.kt @@ -49,6 +49,7 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) { Column( modifier = Modifier .padding(horizontal = 15.dp) + .padding(vertical = 35.dp) .verticalScroll(rememberScrollState()) ) { Row( 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 80d2c5592..6dc195ffc 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 @@ -30,7 +30,6 @@ import java.util.* @SuppressLint("SetJavaScriptEnabled") @Composable fun WebReader( - preferences: WebPreferences, styledContent: String, webReaderViewModel: WebReaderViewModel ) { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index 8410aeb59..56f1263f0 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -121,7 +121,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher var isMenuExpanded by remember { mutableStateOf(false) } - var bottomSheetState by remember { mutableStateOf(app.omnivore.omnivore.ui.reader.BottomSheetState.NONE)} + var bottomSheetState by remember { mutableStateOf(BottomSheetState.NONE) } val currentThemeKey = webReaderViewModel.currentThemeKey.observeAsState() val currentTheme = Themes.values().find { it.themeKey == currentThemeKey.value } @@ -147,11 +147,18 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, } ?: null val modalBottomSheetState = rememberModalBottomSheetState( - ModalBottomSheetValue.Hidden, + initialValue = ModalBottomSheetValue.Hidden, ) val themeTintColor = Color(currentTheme?.foregroundColor ?: 0xFFFFFFFF) + annotation?.let { + bottomSheetState = BottomSheetState.HIGHLIGHTNOTE + coroutineScope.launch { + modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded) + } + } + ModalBottomSheetLayout( modifier = Modifier .statusBarsPadding(), @@ -172,7 +179,27 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, } } BottomSheetState.HIGHLIGHTNOTE -> { - + annotation?.let { annotation -> + BottomSheetUI(title = "Note") { + AnnotationEditView( + initialAnnotation = annotation, + onSave = { + webReaderViewModel.saveAnnotation(it) + coroutineScope.launch { + modalBottomSheetState.hide() + bottomSheetState = BottomSheetState.NONE + } + }, + onCancel = { + webReaderViewModel.cancelAnnotationEdit() + coroutineScope.launch { + modalBottomSheetState.hide() + bottomSheetState = BottomSheetState.NONE + } + } + ) + } + } } BottomSheetState.NONE -> { @@ -215,9 +242,9 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, } webReaderParams?.let { IconButton(onClick = { - bottomSheetState = BottomSheetState.NOTEBOOK coroutineScope.launch { - modalBottomSheetState.show() + bottomSheetState = BottomSheetState.NOTEBOOK + modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded) } }) { Icon( @@ -228,9 +255,9 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, } } IconButton(onClick = { - bottomSheetState = BottomSheetState.PREFERENCES coroutineScope.launch { - modalBottomSheetState.show() + bottomSheetState = BottomSheetState.PREFERENCES + modalBottomSheetState.animateTo(ModalBottomSheetValue.HalfExpanded) } }) { Icon( @@ -239,52 +266,39 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, tint = themeTintColor ) } - IconButton(onClick = { isMenuExpanded = true }) { - Icon( - painter = painterResource(id = R.drawable.dots_horizontal), - contentDescription = null, - tint = themeTintColor - ) - if (isMenuExpanded) { - webReaderParams?.let { params -> - SavedItemContextMenu( - isExpanded = isMenuExpanded, - isArchived = params.item.isArchived, - onDismiss = { isMenuExpanded = false }, - actionHandler = { - webReaderViewModel.handleSavedItemAction( - params.item.savedItemId, - it - ) - } - ) + IconButton(onClick = { isMenuExpanded = true }) { + Icon( + painter = painterResource(id = R.drawable.dots_horizontal), + contentDescription = null, + tint = themeTintColor + ) + if (isMenuExpanded) { + webReaderParams?.let { params -> + SavedItemContextMenu( + isExpanded = isMenuExpanded, + isArchived = params.item.isArchived, + onDismiss = { isMenuExpanded = false }, + actionHandler = { + webReaderViewModel.handleSavedItemAction( + params.item.savedItemId, + it + ) + } + ) + } } } - } }, ) } ) { paddingValues -> if (styledContent != null) { WebReader( - preferences = webReaderViewModel.storedWebPreferences(isSystemInDarkTheme()), styledContent = styledContent, webReaderViewModel = webReaderViewModel ) } - if (annotation != null) { - AnnotationEditView( - initialAnnotation = annotation!!, - onSave = { - webReaderViewModel.saveAnnotation(it) - }, - onCancel = { - webReaderViewModel.cancelAnnotationEdit() - } - ) - } - WebReaderLabelsSelectionSheet(webReaderViewModel) } @@ -296,6 +310,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, } } + @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable fun BottomSheetUI(title: String?, content: @Composable () -> Unit) { @@ -310,7 +325,6 @@ fun BottomSheetUI(title: String?, content: @Composable () -> Unit) { Scaffold( ) { paddingValues -> Box(modifier = Modifier - .padding(paddingValues) .fillMaxSize()) { content() } From f5f355535599760823354aab7f0648e0b4e8d704 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 May 2023 22:36:55 +0800 Subject: [PATCH 12/15] Use the main bottom sheet for the labels --- .../ui/reader/WebReaderLoadingContainer.kt | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index 56f1263f0..cbd278dc7 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -32,6 +32,8 @@ import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import app.omnivore.omnivore.MainActivity import app.omnivore.omnivore.R +import app.omnivore.omnivore.persistence.entities.SavedItemLabel +import app.omnivore.omnivore.ui.components.LabelsSelectionSheetContent import app.omnivore.omnivore.ui.components.WebReaderLabelsSelectionSheet import app.omnivore.omnivore.ui.notebook.NotebookView import app.omnivore.omnivore.ui.notebook.NotebookViewModel @@ -108,7 +110,8 @@ enum class BottomSheetState( NONE(), PREFERENCES(), NOTEBOOK(), - HIGHLIGHTNOTE() + HIGHLIGHTNOTE(), + LABELS(), } @@ -131,6 +134,8 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, val shouldPopView: Boolean by webReaderViewModel.shouldPopViewLiveData.observeAsState(false) val toolbarHeightPx: Float by webReaderViewModel.currentToolbarHeightLiveData.observeAsState(0.0f) + val labels: List by webReaderViewModel.savedItemLabelsLiveData.observeAsState(listOf()) + val maxToolbarHeight = 48.dp webReaderViewModel.maxToolbarHeightPx = with(LocalDensity.current) { maxToolbarHeight.roundToPx().toFloat() } webReaderViewModel.loadItem(slug = slug, requestID = requestID) @@ -159,6 +164,15 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, } } + val showLabelsSelector: Boolean by webReaderViewModel.showLabelsSelectionSheetLiveData.observeAsState(false) + + if (showLabelsSelector) { + bottomSheetState = BottomSheetState.LABELS + coroutineScope.launch { + modalBottomSheetState.animateTo(ModalBottomSheetValue.HalfExpanded) + } + } + ModalBottomSheetLayout( modifier = Modifier .statusBarsPadding(), @@ -201,6 +215,35 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, } } } + app.omnivore.omnivore.ui.reader.BottomSheetState.LABELS -> { + BottomSheetUI(title = "Notebook") { + LabelsSelectionSheetContent( + labels = labels, + initialSelectedLabels = webReaderParams?.labels ?: listOf(), + onCancel = { + coroutineScope.launch { + modalBottomSheetState.hide() + bottomSheetState = BottomSheetState.NONE + } + }, + isLibraryMode = false, + onSave = { + if (it != labels) { + webReaderViewModel.updateSavedItemLabels( + savedItemID = webReaderParams?.item?.savedItemId ?: "", labels = it + ) + } + coroutineScope.launch { + modalBottomSheetState.hide() + bottomSheetState = BottomSheetState.NONE + } + }, + onCreateLabel = { newLabelName, labelHexValue -> + webReaderViewModel.createNewSavedItemLabel(newLabelName, labelHexValue) + } + ) + } + } BottomSheetState.NONE -> { } @@ -298,8 +341,6 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, webReaderViewModel = webReaderViewModel ) } - - WebReaderLabelsSelectionSheet(webReaderViewModel) } LaunchedEffect(shouldPopView) { From 7e70cf283c2ae0c8a3dd23a4179c2eb330fd0ed9 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 May 2023 23:00:38 +0800 Subject: [PATCH 13/15] Better handle the system theme --- .../ui/reader/WebReaderLoadingContainer.kt | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index cbd278dc7..cfbb23703 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -126,6 +126,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, var isMenuExpanded by remember { mutableStateOf(false) } var bottomSheetState by remember { mutableStateOf(BottomSheetState.NONE) } + val isDarkMode = isSystemInDarkTheme() val currentThemeKey = webReaderViewModel.currentThemeKey.observeAsState() val currentTheme = Themes.values().find { it.themeKey == currentThemeKey.value } @@ -155,7 +156,24 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, initialValue = ModalBottomSheetValue.Hidden, ) - val themeTintColor = Color(currentTheme?.foregroundColor ?: 0xFFFFFFFF) + val themeBackgroundColor = currentTheme?.let { + if (it.themeKey == "System" && isDarkMode) { + Color(0xFF000000) + } else if (it.themeKey == "System" ) { + Color(0xFFFFFFFF) + } else { + Color(it.backgroundColor ?: 0xFFFFFFFF) + } + } ?: Color(0xFFFFFFFF) + val themeTintColor = currentTheme?.let { + if (it.themeKey == "System" && isDarkMode) { + Color(0xFFFFFFFF) + } else if (it.themeKey == "System" ) { + Color(0xFF000000) + } else { + Color(it.foregroundColor ?: 0xFF000000) + } + } ?: Color(0xFF000000) annotation?.let { bottomSheetState = BottomSheetState.HIGHLIGHTNOTE @@ -258,7 +276,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, .height(height = with(LocalDensity.current) { toolbarHeightPx.roundToInt().toDp() }), - backgroundColor = Color(currentTheme?.backgroundColor ?: 0xFFFFFFFF), + backgroundColor = themeBackgroundColor, elevation = 0.dp, title = {}, navigationIcon = { From 001a4b25c78d23aa8e9c725f567070258e5dedbc Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Mon, 8 May 2023 23:00:48 +0800 Subject: [PATCH 14/15] Bump the version number --- android/Omnivore/app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index 7efadfe3b..c6cb62da0 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "app.omnivore.omnivore" minSdk 26 targetSdk 33 - versionCode 67 - versionName "0.0.67" + versionCode 68 + versionName "0.0.68" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 7fdcf70a8391356239951ac07cbdfaf439bbe557 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 9 May 2023 11:28:40 +0800 Subject: [PATCH 15/15] Android menu and version --- android/Omnivore/app/build.gradle | 4 +-- .../omnivore/persistence/AppDatabase.kt | 2 +- .../ui/savedItemViews/SavedItemContextMenu.kt | 26 +++++++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index c6cb62da0..0fd2aa093 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "app.omnivore.omnivore" minSdk 26 targetSdk 33 - versionCode 68 - versionName "0.0.68" + versionCode 70 + versionName "0.0.70" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { 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 index 76f7efe5f..905ff30bf 100644 --- 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 @@ -13,7 +13,7 @@ import app.omnivore.omnivore.persistence.entities.* SavedItemAndSavedItemLabelCrossRef::class, SavedItemAndHighlightCrossRef::class ], - version = 6 + version = 7 ) abstract class AppDatabase : RoomDatabase() { abstract fun viewerDao(): ViewerDao diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt index 34a50a11c..acbfb0861 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt @@ -24,19 +24,19 @@ fun SavedItemContextMenu( expanded = isExpanded, onDismissRequest = onDismiss ) { - DropdownMenuItem( - text = { Text("Edit Labels") }, - onClick = { - actionHandler(SavedItemAction.EditLabels) - onDismiss() - }, - leadingIcon = { - Icon( - painter = painterResource(id = R.drawable.tag), - contentDescription = null - ) - } - ) +// DropdownMenuItem( +// text = { Text("Edit Labels") }, +// onClick = { +// actionHandler(SavedItemAction.EditLabels) +// onDismiss() +// }, +// leadingIcon = { +// Icon( +// painter = painterResource(id = R.drawable.tag), +// contentDescription = null +// ) +// } +// ) DropdownMenuItem( text = { Text(if (isArchived) "Unarchive" else "Archive") }, onClick = {