mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #3333 from omnivore-app/fix/android-library-labels
Open the labels sheet from the library on Android
This commit is contained in:
commit
535a5cbd9e
30 changed files with 422 additions and 229 deletions
|
|
@ -19,8 +19,8 @@ android {
|
|||
applicationId "app.omnivore.omnivore"
|
||||
minSdk 26
|
||||
targetSdk 33
|
||||
versionCode 170
|
||||
versionName "0.0.170"
|
||||
versionCode 178
|
||||
versionName "0.0.178"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,12 +1,17 @@
|
|||
package app.omnivore.omnivore.dataService
|
||||
|
||||
import android.util.Log
|
||||
import app.omnivore.omnivore.graphql.generated.type.HighlightType
|
||||
import app.omnivore.omnivore.models.ServerSyncStatus
|
||||
import app.omnivore.omnivore.networking.*
|
||||
import app.omnivore.omnivore.persistence.entities.Highlight
|
||||
import app.omnivore.omnivore.persistence.entities.SavedItemAndHighlightCrossRef
|
||||
import app.omnivore.omnivore.persistence.entities.saveHighlightChange
|
||||
import com.apollographql.apollo3.api.Optional
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
|
||||
|
|
@ -33,6 +38,8 @@ suspend fun DataService.createWebHighlight(jsonString: String, colorName: String
|
|||
|
||||
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
|
||||
|
||||
val highlightChange = saveHighlightChange(db.highlightChangesDao(), createHighlightInput.articleId, highlight)
|
||||
|
||||
val crossRef = SavedItemAndHighlightCrossRef(
|
||||
highlightId = createHighlightInput.id,
|
||||
savedItemId = createHighlightInput.articleId
|
||||
|
|
@ -41,11 +48,7 @@ suspend fun DataService.createWebHighlight(jsonString: String, colorName: String
|
|||
db.highlightDao().insertAll(listOf(highlight))
|
||||
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
|
||||
|
||||
val newHighlight = networker.createHighlight(createHighlightInput)
|
||||
|
||||
newHighlight?.let {
|
||||
db.highlightDao().update(it)
|
||||
}
|
||||
performHighlightChange(highlightChange)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +76,8 @@ suspend fun DataService.createNoteHighlight(savedItemId: String, note: String):
|
|||
|
||||
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
|
||||
|
||||
val highlightChange = saveHighlightChange(db.highlightChangesDao(), savedItemId, highlight)
|
||||
|
||||
val crossRef = SavedItemAndHighlightCrossRef(
|
||||
highlightId = createHighlightId,
|
||||
savedItemId = savedItemId
|
||||
|
|
@ -81,21 +86,7 @@ suspend fun DataService.createNoteHighlight(savedItemId: String, note: String):
|
|||
db.highlightDao().insertAll(listOf(highlight))
|
||||
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
|
||||
|
||||
val newHighlight = networker.createHighlight(input = CreateHighlightParams(
|
||||
type = HighlightType.NOTE,
|
||||
articleId = savedItemId,
|
||||
id = createHighlightId,
|
||||
shortId = shortId,
|
||||
quote = null,
|
||||
patch = null,
|
||||
annotation = note,
|
||||
highlightPositionAnchorIndex = 0,
|
||||
highlightPositionPercent = 0.0
|
||||
).asCreateHighlightInput())
|
||||
|
||||
newHighlight?.let {
|
||||
db.highlightDao().update(it)
|
||||
}
|
||||
performHighlightChange(highlightChange)
|
||||
}
|
||||
|
||||
return createHighlightId
|
||||
|
|
@ -103,18 +94,33 @@ suspend fun DataService.createNoteHighlight(savedItemId: String, note: String):
|
|||
|
||||
suspend fun DataService.mergeWebHighlights(jsonString: String) {
|
||||
val mergeHighlightInput = Gson().fromJson(jsonString, MergeHighlightsParams::class.java).asMergeHighlightInput()
|
||||
Log.d("sync", "mergeHighlightInput: " + mergeHighlightInput.id + ": " + mergeHighlightInput)
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val highlight = db.highlightDao().findById(highlightId = mergeHighlightInput.id) ?: return@withContext
|
||||
highlight.shortId = mergeHighlightInput.shortId
|
||||
highlight.quote = mergeHighlightInput.quote
|
||||
highlight.patch = mergeHighlightInput.patch
|
||||
highlight.prefix = mergeHighlightInput.prefix.getOrNull()
|
||||
highlight.annotation = mergeHighlightInput.annotation.getOrNull()
|
||||
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_UPDATE.rawValue
|
||||
val highlight = Highlight(
|
||||
type = "HIGHLIGHT",
|
||||
highlightId = mergeHighlightInput.id,
|
||||
shortId = mergeHighlightInput.shortId,
|
||||
quote = mergeHighlightInput.quote,
|
||||
prefix = null,
|
||||
suffix = null,
|
||||
patch = mergeHighlightInput.patch,
|
||||
annotation = mergeHighlightInput.annotation.getOrNull(),
|
||||
createdAt = null,
|
||||
updatedAt = null,
|
||||
createdByMe = false,
|
||||
color = mergeHighlightInput.color.getOrNull(),
|
||||
highlightPositionPercent = mergeHighlightInput.highlightPositionPercent.getOrNull() ?: 0.0,
|
||||
highlightPositionAnchorIndex = mergeHighlightInput.highlightPositionAnchorIndex.getOrNull() ?: 0
|
||||
)
|
||||
|
||||
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
|
||||
|
||||
saveHighlightChange(db.highlightChangesDao(), mergeHighlightInput.articleId, highlight)
|
||||
|
||||
Log.d("sync", "overlapHighlightIdList: " + mergeHighlightInput.overlapHighlightIdList)
|
||||
for (highlightID in mergeHighlightInput.overlapHighlightIdList) {
|
||||
deleteHighlight(highlightID)
|
||||
deleteHighlight(mergeHighlightInput.articleId, highlightID)
|
||||
}
|
||||
|
||||
val crossRef = SavedItemAndHighlightCrossRef(
|
||||
|
|
@ -122,11 +128,10 @@ suspend fun DataService.mergeWebHighlights(jsonString: String) {
|
|||
savedItemId = mergeHighlightInput.articleId
|
||||
)
|
||||
|
||||
db.highlightDao().insertAll(listOf(highlight))
|
||||
db.savedItemAndHighlightCrossRefDao().insertAll(listOf(crossRef))
|
||||
db.highlightDao().update(highlight)
|
||||
|
||||
val isUpdatedOnServer = networker.mergeHighlights(mergeHighlightInput)
|
||||
|
||||
if (isUpdatedOnServer) {
|
||||
highlight.serverSyncStatus = ServerSyncStatus.IS_SYNCED.rawValue
|
||||
db.highlightDao().update(highlight)
|
||||
|
|
@ -135,42 +140,40 @@ suspend fun DataService.mergeWebHighlights(jsonString: String) {
|
|||
}
|
||||
|
||||
suspend fun DataService.updateWebHighlight(jsonString: String) {
|
||||
val updateHighlightParams = Gson().fromJson(jsonString, UpdateHighlightParams::class.java).asUpdateHighlightInput()
|
||||
val updateHighlightParams = Gson().fromJson(jsonString, UpdateHighlightParams::class.java)
|
||||
|
||||
if (updateHighlightParams.highlightId == null || updateHighlightParams.libraryItemId == null) {
|
||||
Log.d("error","ERROR INVALID HIGHLIGHT DATA")
|
||||
return
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
val highlight = db.highlightDao().findById(highlightId = updateHighlightParams.highlightId) ?: return@withContext
|
||||
val highlight = db.highlightDao().findById(highlightId = updateHighlightParams.highlightId ?: "") ?: return@withContext
|
||||
|
||||
highlight.annotation = updateHighlightParams.annotation.getOrNull()
|
||||
highlight.annotation = updateHighlightParams.annotation
|
||||
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_UPDATE.rawValue
|
||||
db.highlightDao().update(highlight)
|
||||
|
||||
val isUpdatedOnServer = networker.updateHighlight(updateHighlightParams)
|
||||
|
||||
if (isUpdatedOnServer) {
|
||||
highlight.serverSyncStatus = ServerSyncStatus.IS_SYNCED.rawValue
|
||||
db.highlightDao().update(highlight)
|
||||
}
|
||||
val highlightChange = saveHighlightChange(db.highlightChangesDao(), updateHighlightParams.libraryItemId ?: "", highlight)
|
||||
performHighlightChange(highlightChange)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun DataService.deleteHighlights(jsonString: String) {
|
||||
val highlightIDs = Gson().fromJson(jsonString, DeleteHighlightParams::class.java).asIdList()
|
||||
|
||||
for (highlightID in highlightIDs) {
|
||||
deleteHighlight(highlightID)
|
||||
}
|
||||
suspend fun DataService.deleteHighlightFromJSON(jsonString: String) {
|
||||
val deleteHighlightParams = Gson().fromJson(jsonString, DeleteHighlightParams::class.java)
|
||||
deleteHighlight(deleteHighlightParams.libraryItemId, deleteHighlightParams.highlightId)
|
||||
}
|
||||
|
||||
private suspend fun DataService.deleteHighlight(highlightID: String) {
|
||||
private suspend fun DataService.deleteHighlight(savedItemId: String, highlightID: String) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val highlight = db.highlightDao().findById(highlightId = highlightID) ?: return@withContext
|
||||
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_DELETION.rawValue
|
||||
db.highlightDao().update(highlight)
|
||||
val highlight = db.highlightDao().findById(highlightId = highlightID)
|
||||
|
||||
val isUpdatedOnServer = networker.deleteHighlights(listOf(highlightID))
|
||||
highlight?.let {
|
||||
highlight.serverSyncStatus = ServerSyncStatus.NEEDS_DELETION.rawValue
|
||||
db.highlightDao().update(highlight)
|
||||
|
||||
if (isUpdatedOnServer) {
|
||||
db.highlightDao().deleteById(highlightId = highlightID)
|
||||
val highlightChange = saveHighlightChange(db.highlightChangesDao(), savedItemId, highlight)
|
||||
performHighlightChange(highlightChange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,44 @@
|
|||
package app.omnivore.omnivore.dataService
|
||||
|
||||
import android.util.Log
|
||||
import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput
|
||||
import app.omnivore.omnivore.graphql.generated.type.UpdateHighlightInput
|
||||
import app.omnivore.omnivore.models.ServerSyncStatus
|
||||
import app.omnivore.omnivore.networking.*
|
||||
import app.omnivore.omnivore.persistence.entities.Highlight
|
||||
import app.omnivore.omnivore.persistence.entities.HighlightChange
|
||||
import app.omnivore.omnivore.persistence.entities.SavedItem
|
||||
import app.omnivore.omnivore.persistence.entities.highlightChangeToHighlight
|
||||
import com.apollographql.apollo3.api.Optional
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.math.log
|
||||
|
||||
suspend fun DataService.startSyncChannels() {
|
||||
Log.d("sync", "Starting sync channels")
|
||||
for (savedItem in savedItemSyncChannel) {
|
||||
syncSavedItem(savedItem)
|
||||
}
|
||||
}
|
||||
|
||||
for (highlight in highlightSyncChannel) {
|
||||
syncHighlight(highlight)
|
||||
suspend fun DataService.performHighlightChange(highlightChange: HighlightChange) {
|
||||
val highlight = highlightChangeToHighlight(highlightChange)
|
||||
if (syncHighlightChange(highlightChange)) {
|
||||
db.highlightChangesDao().deleteById(highlight.highlightId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun DataService.syncOfflineItemsWithServerIfNeeded() {
|
||||
val unSyncedSavedItems = db.savedItemDao().getUnSynced()
|
||||
val unSyncedHighlights = db.highlightDao().getUnSynced()
|
||||
val unSyncedHighlights = db.highlightChangesDao().getUnSynced()
|
||||
|
||||
for (savedItem in unSyncedSavedItems) {
|
||||
delay(250)
|
||||
savedItemSyncChannel.send(savedItem)
|
||||
}
|
||||
|
||||
for (highlight in unSyncedHighlights) {
|
||||
delay(250)
|
||||
highlightSyncChannel.send(highlight)
|
||||
for (change in unSyncedHighlights) {
|
||||
performHighlightChange(change)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +90,9 @@ private suspend fun DataService.syncSavedItem(item: SavedItem) {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun DataService.syncHighlight(highlight: Highlight) {
|
||||
private suspend fun DataService.syncHighlightChange(highlightChange: HighlightChange): Boolean {
|
||||
val highlight = highlightChangeToHighlight(highlightChange)
|
||||
|
||||
fun updateSyncStatus(status: ServerSyncStatus) {
|
||||
highlight.serverSyncStatus = status.rawValue
|
||||
db.highlightDao().update(highlight)
|
||||
|
|
@ -91,7 +101,6 @@ private suspend fun DataService.syncHighlight(highlight: Highlight) {
|
|||
when (highlight.serverSyncStatus) {
|
||||
ServerSyncStatus.NEEDS_DELETION.rawValue -> {
|
||||
updateSyncStatus(ServerSyncStatus.IS_SYNCING)
|
||||
|
||||
val isDeletedOnServer = networker.deleteHighlights(listOf(highlight.highlightId))
|
||||
|
||||
if (isDeletedOnServer) {
|
||||
|
|
@ -99,8 +108,12 @@ private suspend fun DataService.syncHighlight(highlight: Highlight) {
|
|||
} else {
|
||||
updateSyncStatus(ServerSyncStatus.NEEDS_DELETION)
|
||||
}
|
||||
return isDeletedOnServer != null
|
||||
}
|
||||
|
||||
ServerSyncStatus.NEEDS_UPDATE.rawValue -> {
|
||||
Log.d("sync", "creating highlight update change: ${highlightChange}")
|
||||
|
||||
updateSyncStatus(ServerSyncStatus.IS_SYNCING)
|
||||
|
||||
val isUpdatedOnServer = networker.updateHighlight(
|
||||
|
|
@ -110,36 +123,40 @@ private suspend fun DataService.syncHighlight(highlight: Highlight) {
|
|||
sharedAt = Optional.absent()
|
||||
)
|
||||
)
|
||||
Log.d("sync", "sycn.updateHighlight result: ${isUpdatedOnServer}")
|
||||
|
||||
if (isUpdatedOnServer) {
|
||||
updateSyncStatus(ServerSyncStatus.IS_SYNCED)
|
||||
} else {
|
||||
updateSyncStatus(ServerSyncStatus.NEEDS_UPDATE)
|
||||
}
|
||||
return isUpdatedOnServer != null
|
||||
}
|
||||
|
||||
ServerSyncStatus.NEEDS_CREATION.rawValue -> {
|
||||
Log.d("sync", "creating highlight create change: ${highlightChange}")
|
||||
updateSyncStatus(ServerSyncStatus.IS_SYNCING)
|
||||
|
||||
val savedItemID = db.savedItemAndHighlightCrossRefDao()
|
||||
.associatedSavedItemID(highlightId = highlight.highlightId)
|
||||
|
||||
val isCreatedOnServer = networker.createHighlight(
|
||||
val createResult = networker.createHighlight(
|
||||
CreateHighlightInput(
|
||||
annotation = Optional.presentIfNotNull(highlight.annotation),
|
||||
articleId = savedItemID ?: "",
|
||||
articleId = highlightChange.savedItemId,
|
||||
id = highlight.highlightId,
|
||||
patch = Optional.presentIfNotNull(highlight.patch),
|
||||
quote = Optional.presentIfNotNull(highlight.quote),
|
||||
shortId = highlight.shortId
|
||||
)
|
||||
)
|
||||
Log.d("sync", "sycn.createResult: " + createResult)
|
||||
|
||||
if (isCreatedOnServer != null) {
|
||||
if (createResult.newHighlight != null || createResult.alreadyExists) {
|
||||
updateSyncStatus(ServerSyncStatus.IS_SYNCED)
|
||||
return true
|
||||
} else {
|
||||
updateSyncStatus(ServerSyncStatus.NEEDS_UPDATE)
|
||||
return false
|
||||
}
|
||||
}
|
||||
else -> return
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import app.omnivore.omnivore.graphql.generated.CreateHighlightMutation
|
|||
import app.omnivore.omnivore.graphql.generated.DeleteHighlightMutation
|
||||
import app.omnivore.omnivore.graphql.generated.MergeHighlightMutation
|
||||
import app.omnivore.omnivore.graphql.generated.UpdateHighlightMutation
|
||||
import app.omnivore.omnivore.graphql.generated.type.CreateHighlightErrorCode
|
||||
import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput
|
||||
import app.omnivore.omnivore.graphql.generated.type.HighlightType
|
||||
import app.omnivore.omnivore.graphql.generated.type.MergeHighlightInput
|
||||
|
|
@ -39,6 +40,7 @@ data class CreateHighlightParams(
|
|||
|
||||
data class UpdateHighlightParams(
|
||||
val highlightId: String?,
|
||||
val libraryItemId: String?,
|
||||
val `annotation`: String?,
|
||||
val sharedAt: String?,
|
||||
) {
|
||||
|
|
@ -73,9 +75,10 @@ data class MergeHighlightsParams(
|
|||
}
|
||||
|
||||
data class DeleteHighlightParams(
|
||||
val highlightId: String?
|
||||
val highlightId: String,
|
||||
val libraryItemId: String
|
||||
) {
|
||||
fun asIdList() = listOf(highlightId ?: "")
|
||||
fun asIdList() = listOf(highlightId)
|
||||
}
|
||||
|
||||
suspend fun Networker.deleteHighlight(jsonString: String): Boolean {
|
||||
|
|
@ -134,18 +137,26 @@ suspend fun Networker.createWebHighlight(jsonString: String): Boolean {
|
|||
return createHighlight(input) != null
|
||||
}
|
||||
|
||||
suspend fun Networker.createHighlight(input: CreateHighlightInput): Highlight? {
|
||||
Log.d("Loggo", "created highlight input: $input")
|
||||
data class CreateHighlightResult(
|
||||
val failedToCreate: Boolean,
|
||||
val alreadyExists: Boolean,
|
||||
val newHighlight: Highlight?
|
||||
)
|
||||
|
||||
suspend fun Networker.createHighlight(input: CreateHighlightInput): CreateHighlightResult {
|
||||
Log.d("sync", "creating highlight with input: ${input}")
|
||||
|
||||
try {
|
||||
val result = authenticatedApolloClient().mutation(CreateHighlightMutation(input)).execute()
|
||||
Log.d("Loggo", "result: ${result.data}")
|
||||
|
||||
Log.d("sync", "result: ${result.data}")
|
||||
|
||||
val createdHighlight = result.data?.createHighlight?.onCreateHighlightSuccess?.highlight
|
||||
|
||||
if (createdHighlight != null) {
|
||||
return Highlight(
|
||||
return CreateHighlightResult(
|
||||
failedToCreate = false,
|
||||
alreadyExists = false,
|
||||
newHighlight = Highlight(
|
||||
type = createdHighlight.highlightFields.type.toString(),
|
||||
highlightId = createdHighlight.highlightFields.id,
|
||||
shortId = createdHighlight.highlightFields.shortId,
|
||||
|
|
@ -161,10 +172,22 @@ suspend fun Networker.createHighlight(input: CreateHighlightInput): Highlight? {
|
|||
highlightPositionPercent = createdHighlight.highlightFields.highlightPositionPercent,
|
||||
highlightPositionAnchorIndex = createdHighlight.highlightFields.highlightPositionAnchorIndex
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
if (result.data?.createHighlight?.onCreateHighlightError?.errorCodes?.first() == CreateHighlightErrorCode.ALREADY_EXISTS) {
|
||||
return CreateHighlightResult(
|
||||
failedToCreate = false,
|
||||
alreadyExists = true,
|
||||
newHighlight = null
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: java.lang.Exception) {
|
||||
return null
|
||||
Log.d("sync", "error creating highlight: " +e)
|
||||
}
|
||||
return CreateHighlightResult(
|
||||
failedToCreate = true,
|
||||
alreadyExists = false,
|
||||
newHighlight = null
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,15 +10,17 @@ import app.omnivore.omnivore.persistence.entities.*
|
|||
SavedItem::class,
|
||||
SavedItemLabel::class,
|
||||
Highlight::class,
|
||||
HighlightChange::class,
|
||||
SavedItemAndSavedItemLabelCrossRef::class,
|
||||
SavedItemAndHighlightCrossRef::class
|
||||
],
|
||||
version = 15
|
||||
version = 20
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun viewerDao(): ViewerDao
|
||||
abstract fun savedItemDao(): SavedItemDao
|
||||
abstract fun highlightDao(): HighlightDao
|
||||
abstract fun highlightChangesDao(): HighlightChangesDao
|
||||
abstract fun savedItemLabelDao(): SavedItemLabelDao
|
||||
abstract fun savedItemWithLabelsAndHighlightsDao(): SavedItemWithLabelsAndHighlightsDao
|
||||
abstract fun savedItemAndSavedItemLabelCrossRefDao(): SavedItemAndSavedItemLabelCrossRefDao
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
package app.omnivore.omnivore.persistence.entities
|
||||
|
||||
import android.util.Log
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.Query
|
||||
import app.omnivore.omnivore.models.ServerSyncStatus
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Entity
|
||||
data class HighlightChange(
|
||||
@PrimaryKey val highlightId: String,
|
||||
val savedItemId: String,
|
||||
|
||||
val type: String,
|
||||
var annotation: String?,
|
||||
val createdAt: String?,
|
||||
val createdByMe: Boolean = true,
|
||||
val markedForDeletion: Boolean = false,
|
||||
var patch: String?,
|
||||
var prefix: String?,
|
||||
var quote: String?,
|
||||
var serverSyncStatus: Int = ServerSyncStatus.IS_SYNCED.rawValue,
|
||||
var shortId: String,
|
||||
val suffix: String?,
|
||||
val updatedAt: String?,
|
||||
val color: String?,
|
||||
val highlightPositionPercent: Double?,
|
||||
val highlightPositionAnchorIndex: Int?
|
||||
)
|
||||
|
||||
fun saveHighlightChange(dao: HighlightChangesDao, savedItemId: String, highlight: Highlight): HighlightChange {
|
||||
Log.d("sync", "saving highlight change: " + savedItemId + ", " + highlight)
|
||||
val change = HighlightChange(
|
||||
savedItemId = savedItemId,
|
||||
highlightId = highlight.highlightId,
|
||||
type = highlight.type,
|
||||
shortId = highlight.shortId,
|
||||
quote = highlight.quote,
|
||||
prefix = highlight.prefix,
|
||||
suffix = highlight.suffix,
|
||||
patch = highlight.patch,
|
||||
annotation = highlight.annotation,
|
||||
createdAt = highlight.createdAt,
|
||||
updatedAt = highlight.updatedAt,
|
||||
createdByMe = highlight.createdByMe,
|
||||
color =highlight.color,
|
||||
highlightPositionPercent = highlight.highlightPositionPercent,
|
||||
highlightPositionAnchorIndex = highlight.highlightPositionAnchorIndex,
|
||||
serverSyncStatus = highlight.serverSyncStatus
|
||||
)
|
||||
dao.insertAll(listOf(change))
|
||||
return change
|
||||
}
|
||||
|
||||
fun highlightChangeToHighlight(change: HighlightChange): Highlight {
|
||||
return Highlight(
|
||||
highlightId = change.highlightId,
|
||||
type = change.type,
|
||||
shortId = change.shortId,
|
||||
quote = change.quote,
|
||||
prefix = change.prefix,
|
||||
suffix = change.suffix,
|
||||
patch = change.patch,
|
||||
annotation = change.annotation,
|
||||
createdAt = change.createdAt,
|
||||
updatedAt = change.updatedAt,
|
||||
createdByMe = change.createdByMe,
|
||||
color = change.color,
|
||||
highlightPositionPercent = change.highlightPositionPercent,
|
||||
highlightPositionAnchorIndex = change.highlightPositionAnchorIndex,
|
||||
serverSyncStatus = change.serverSyncStatus
|
||||
)
|
||||
}
|
||||
|
||||
@Dao
|
||||
interface HighlightChangesDao {
|
||||
@Query("SELECT * FROM highlightChange WHERE serverSyncStatus != 0 ORDER BY updatedAt ASC")
|
||||
fun getUnSynced(): List<HighlightChange>
|
||||
|
||||
@Query("DELETE FROM highlightChange WHERE highlightId = :highlightId")
|
||||
fun deleteById(highlightId: String)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertAll(items: List<HighlightChange>)
|
||||
}
|
||||
|
||||
|
|
@ -27,6 +27,7 @@ import app.omnivore.omnivore.R
|
|||
import app.omnivore.omnivore.ui.save.SaveState
|
||||
import app.omnivore.omnivore.ui.save.SaveViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AddLinkSheetContent(
|
||||
viewModel: SaveViewModel,
|
||||
|
|
@ -84,42 +85,47 @@ fun AddLinkSheetContent(
|
|||
viewModel.saveURL(url)
|
||||
}
|
||||
|
||||
Surface(
|
||||
androidx.compose.material.Scaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
) {
|
||||
.background(MaterialTheme.colorScheme.primaryContainer),
|
||||
topBar = {
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.add_link_sheet_title))
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.background
|
||||
),
|
||||
navigationIcon = {
|
||||
TextButton(onClick = onCancel) {
|
||||
Text(text = stringResource(R.string.label_selection_sheet_action_cancel))
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
TextButton(onClick = { addLink(textFieldValue.text) }) {
|
||||
Text(stringResource(R.string.add_link_sheet_action_add_link))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 5.dp)
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(horizontal = 10.dp)
|
||||
) {
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
TextButton(onClick = onCancel) {
|
||||
Text(text = stringResource(R.string.add_link_sheet_action_cancel))
|
||||
}
|
||||
|
||||
Text(stringResource(R.string.add_link_sheet_title), fontWeight = FontWeight.ExtraBold)
|
||||
|
||||
TextButton(onClick = { addLink(textFieldValue.text) }) {
|
||||
Text(stringResource(R.string.add_link_sheet_action_add_link))
|
||||
}
|
||||
}
|
||||
|
||||
if (isSaving.value == true) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.height(16.dp)
|
||||
.width(16.dp),
|
||||
|
||||
strokeWidth = 2.dp,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
|
|
@ -129,18 +135,29 @@ fun AddLinkSheetContent(
|
|||
value = textFieldValue,
|
||||
placeholder = { Text(stringResource(R.string.add_link_sheet_text_field_placeholder)) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
||||
leadingIcon = { Icon(imageVector = Icons.Default.Link, contentDescription = "linkIcon") },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Link,
|
||||
contentDescription = "linkIcon"
|
||||
)
|
||||
},
|
||||
onValueChange = { textFieldValue = it },
|
||||
modifier = Modifier.focusRequester(focusRequester).padding(top = 24.dp).fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.focusRequester(focusRequester)
|
||||
.padding(top = 24.dp)
|
||||
.padding(horizontal = 10.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
if (clipboardText != null) {
|
||||
Button(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
modifier = Modifier.padding(top = 10.dp) .padding(horizontal = 10.dp)
|
||||
,
|
||||
onClick = {
|
||||
textFieldValue = TextFieldValue(
|
||||
text = clipboardText,
|
||||
selection = TextRange(clipboardText.length))
|
||||
selection = TextRange(clipboardText.length)
|
||||
)
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.add_link_sheet_action_paste_from_clipboard))
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -20,15 +21,22 @@ import androidx.compose.foundation.rememberScrollState
|
|||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.TextFieldDefaults
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AddCircle
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SmallTopAppBar
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
|
|
@ -49,6 +57,7 @@ import androidx.compose.ui.platform.LocalDensity
|
|||
import androidx.compose.ui.platform.LocalViewConfiguration
|
||||
import androidx.compose.ui.platform.ViewConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
|
|
@ -56,6 +65,7 @@ import androidx.compose.ui.text.toLowerCase
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import app.omnivore.omnivore.R
|
||||
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
|
||||
import com.dokar.chiptextfield.Chip
|
||||
|
|
@ -279,40 +289,43 @@ fun LabelsSelectionSheetContent(
|
|||
stringResource(R.string.label_selection_sheet_title) else
|
||||
stringResource(R.string.label_selection_sheet_title_alt)
|
||||
|
||||
Surface(
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
) {
|
||||
.background(MaterialTheme.colorScheme.primaryContainer),
|
||||
topBar = {
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
Text(titleText)
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.background
|
||||
),
|
||||
navigationIcon = {
|
||||
TextButton(onClick = onCancel) {
|
||||
Text(text = stringResource(R.string.label_selection_sheet_action_cancel))
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
TextButton(onClick = { onSave(state.chips.map { it.label }) }) {
|
||||
Text(
|
||||
text = if (isLibraryMode)
|
||||
stringResource(R.string.label_selection_sheet_action_search) else
|
||||
stringResource(R.string.label_selection_sheet_action_save)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 5.dp)
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(horizontal = 10.dp)
|
||||
) {
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
TextButton(onClick = onCancel) {
|
||||
Text(text = stringResource(R.string.label_selection_sheet_action_cancel))
|
||||
}
|
||||
|
||||
Text(titleText, fontWeight = FontWeight.ExtraBold)
|
||||
|
||||
TextButton(onClick = { onSave(state.chips.map { it.label }) }) {
|
||||
Text(
|
||||
text = if (isLibraryMode)
|
||||
stringResource(R.string.label_selection_sheet_action_search) else
|
||||
stringResource(R.string.label_selection_sheet_action_save)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ChipTextField(
|
||||
state = state,
|
||||
value = filterTextValue,
|
||||
|
|
@ -344,12 +357,13 @@ fun LabelsSelectionSheetContent(
|
|||
),
|
||||
colors = TextFieldDefaults.textFieldColors(
|
||||
textColor = MaterialTheme.colorScheme.onBackground,
|
||||
backgroundColor = MaterialTheme.colorScheme.background
|
||||
backgroundColor = MaterialTheme.colorScheme.surface
|
||||
),
|
||||
contentPadding = PaddingValues(10.dp),
|
||||
modifier = Modifier
|
||||
.defaultMinSize(minHeight = 45.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(top = 24.dp)
|
||||
.padding(horizontal = 10.dp)
|
||||
.focusRequester(focusRequester)
|
||||
// .onFocusEvent {
|
||||
|
|
@ -397,19 +411,23 @@ fun LabelsSelectionSheetContent(
|
|||
}
|
||||
}
|
||||
.padding(horizontal = 10.dp)
|
||||
.padding(top = 10.dp, bottom = 5.dp)
|
||||
.padding(top = 24.dp, bottom = 5.dp)
|
||||
)
|
||||
{
|
||||
Icon(
|
||||
imageVector = Icons.Filled.AddCircle,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.label_selection_sheet_text_create,
|
||||
filterTextValue.text.trim()
|
||||
)
|
||||
),
|
||||
style = TextStyle(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import app.omnivore.omnivore.R
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun EditInfoSheetContent(
|
||||
savedItemId: String?,
|
||||
|
|
@ -68,44 +69,47 @@ fun EditInfoSheetContent(
|
|||
}
|
||||
}
|
||||
|
||||
Surface(
|
||||
androidx.compose.material.Scaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
) {
|
||||
.background(MaterialTheme.colorScheme.primaryContainer),
|
||||
topBar = {
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.edit_info_sheet_title))
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.background
|
||||
),
|
||||
navigationIcon = {
|
||||
TextButton(onClick = onCancel) {
|
||||
Text(text = stringResource(R.string.edit_info_sheet_action_cancel))
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
TextButton(onClick = {
|
||||
val newTitle = titleTextFieldValue.text
|
||||
val newAuthor = authorTextFieldValue.text.ifEmpty { null }
|
||||
val newDescription = descriptionTextFieldValue.text.ifEmpty { null }
|
||||
|
||||
savedItemId?.let {
|
||||
viewModel.editInfo(it, newTitle, newAuthor, newDescription)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.edit_info_sheet_action_save))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp)
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(horizontal = 10.dp)
|
||||
) {
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
TextButton(onClick = onCancel) {
|
||||
Text(text = stringResource(R.string.edit_info_sheet_action_cancel))
|
||||
}
|
||||
|
||||
Text(stringResource(R.string.edit_info_sheet_title), fontWeight = FontWeight.ExtraBold)
|
||||
|
||||
TextButton(onClick = {
|
||||
val newTitle = titleTextFieldValue.text
|
||||
val newAuthor = authorTextFieldValue.text.ifEmpty { null }
|
||||
val newDescription = descriptionTextFieldValue.text.ifEmpty { null }
|
||||
|
||||
savedItemId?.let {
|
||||
viewModel.editInfo(it, newTitle, newAuthor, newDescription)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(R.string.edit_info_sheet_action_save))
|
||||
}
|
||||
}
|
||||
|
||||
if (isUpdating.value == true) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
CircularProgressIndicator(
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import androidx.compose.material.DismissValue
|
|||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.FractionalThreshold
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.ModalBottomSheetValue
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.ScaffoldState
|
||||
import androidx.compose.material.SwipeToDismiss
|
||||
|
|
@ -31,6 +32,7 @@ import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
|||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material.rememberDismissState
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -144,14 +146,21 @@ fun showAddLinkBottomSheet(libraryViewModel: LibraryViewModel) {
|
|||
libraryViewModel.bottomSheetState.value = LibraryBottomSheetState.ADD_LINK
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun LabelBottomSheet(
|
||||
libraryViewModel: LibraryViewModel,
|
||||
labelsViewModel: LabelsViewModel,
|
||||
onDismiss: () -> Unit = {}
|
||||
) {
|
||||
ModalBottomSheet(onDismissRequest = { onDismiss() }) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { onDismiss() },
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
sheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true
|
||||
),
|
||||
) {
|
||||
|
||||
val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit()
|
||||
val labels: List<SavedItemLabel> by libraryViewModel.savedItemLabelsLiveData.observeAsState(
|
||||
listOf()
|
||||
|
|
@ -206,7 +215,14 @@ fun AddLinkBottomSheet(
|
|||
saveViewModel: SaveViewModel,
|
||||
onDismiss: () -> Unit = {}
|
||||
) {
|
||||
ModalBottomSheet(onDismissRequest = { onDismiss() }) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { onDismiss() },
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
sheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true
|
||||
),
|
||||
) {
|
||||
|
||||
AddLinkSheetContent(
|
||||
viewModel = saveViewModel,
|
||||
onCancel = {
|
||||
|
|
@ -228,7 +244,13 @@ fun EditBottomSheet(
|
|||
libraryViewModel: LibraryViewModel,
|
||||
onDismiss: () -> Unit = {}
|
||||
) {
|
||||
ModalBottomSheet(onDismissRequest = { onDismiss() }) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { onDismiss() },
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
sheetState = rememberModalBottomSheetState(
|
||||
skipPartiallyExpanded = true
|
||||
),
|
||||
) {
|
||||
val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit()
|
||||
EditInfoSheetContent(
|
||||
savedItemId = currentSavedItemData?.savedItem?.savedItemId,
|
||||
|
|
|
|||
|
|
@ -300,7 +300,7 @@ class LibraryViewModel @Inject constructor(
|
|||
|
||||
SavedItemAction.EditLabels -> {
|
||||
currentItemLiveData.value = itemID
|
||||
bottomSheetState.value = LibraryBottomSheetState.EDIT
|
||||
bottomSheetState.value = LibraryBottomSheetState.LABEL
|
||||
}
|
||||
|
||||
SavedItemAction.EditInfo -> {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
|
|||
|
||||
val selectedWebFontName = remember { mutableStateOf(currentWebPreferences.fontFamily.displayText) }
|
||||
|
||||
|
||||
var fontSizeSliderValue by remember { mutableStateOf(currentWebPreferences.textFontSize.toFloat()) }
|
||||
var marginSliderValue by remember { mutableStateOf(currentWebPreferences.maxWidthPercentage.toFloat()) }
|
||||
var lineSpacingSliderValue by remember { mutableStateOf(currentWebPreferences.lineHeight.toFloat()) }
|
||||
|
|
@ -112,8 +111,8 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
|
|||
fontSizeSliderValue = it
|
||||
webReaderViewModel.setFontSize(it.toInt())
|
||||
},
|
||||
steps = 10,
|
||||
valueRange = 10f..48f,
|
||||
steps = 40,
|
||||
valueRange = 10f..50f,
|
||||
)
|
||||
|
||||
Text(stringResource(R.string.reader_preferences_view_margin), style = TextStyle(
|
||||
|
|
@ -127,7 +126,7 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
|
|||
marginSliderValue = it
|
||||
webReaderViewModel.setMaxWidthPercentage(it.toInt())
|
||||
},
|
||||
steps = 4,
|
||||
steps = 40,
|
||||
valueRange = 60f..100f,
|
||||
)
|
||||
|
||||
|
|
@ -142,7 +141,7 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
|
|||
lineSpacingSliderValue = it
|
||||
webReaderViewModel.setLineHeight(it.toInt())
|
||||
},
|
||||
steps = 8,
|
||||
steps = 50,
|
||||
valueRange = 100f..300f,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.compose.foundation.isSystemInDarkTheme
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
|
|
|
|||
|
|
@ -333,7 +333,7 @@ class WebReaderViewModel @Inject constructor(
|
|||
"deleteHighlight" -> {
|
||||
Log.d("Loggo", "receive delete highlight action: $jsonString")
|
||||
viewModelScope.launch {
|
||||
dataService.deleteHighlights(jsonString)
|
||||
dataService.deleteHighlightFromJSON(jsonString)
|
||||
}
|
||||
}
|
||||
"updateHighlight" -> {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavHostController
|
||||
import app.omnivore.omnivore.BuildConfig
|
||||
import app.omnivore.omnivore.R
|
||||
import app.omnivore.omnivore.Routes
|
||||
import app.omnivore.omnivore.ui.auth.LoginViewModel
|
||||
|
|
@ -64,9 +66,12 @@ fun SettingsViewContent(loginViewModel: LoginViewModel, settingsViewModel: Setti
|
|||
Box(
|
||||
modifier = modifier.fillMaxSize()
|
||||
) {
|
||||
|
||||
val version = "Omnivore Version: " + BuildConfig.VERSION_NAME
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
horizontalAlignment = Alignment.Start,
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.fillMaxSize()
|
||||
|
|
@ -74,23 +79,6 @@ fun SettingsViewContent(loginViewModel: LoginViewModel, settingsViewModel: Setti
|
|||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
|
||||
// profile pic and name
|
||||
|
||||
// SettingRow(text = "Labels") { Log.d("settings", "labels button tapped") }
|
||||
// RowDivider()
|
||||
// SettingRow(text = "Emails") { Log.d("settings", "emails button tapped") }
|
||||
// RowDivider()
|
||||
// SettingRow(text = "Subscriptions") { Log.d("settings", "subscriptions button tapped") }
|
||||
// RowDivider()
|
||||
// SettingRow(text = "Clubs") { Log.d("settings", "clubs button tapped") }
|
||||
|
||||
// SectionSpacer()
|
||||
|
||||
// SettingRow(text = "Push Notifications") { Log.d("settings", "pn button tapped") }
|
||||
// RowDivider()
|
||||
// SettingRow(text = "Text to Speech") { Log.d("settings", "tts button tapped") }
|
||||
//
|
||||
// SectionSpacer()
|
||||
|
||||
SettingRow(text = stringResource(R.string.settings_view_setting_row_documentation)) {
|
||||
navController.navigate(Routes.Documentation.route)
|
||||
|
|
@ -118,6 +106,13 @@ fun SettingsViewContent(loginViewModel: LoginViewModel, settingsViewModel: Setti
|
|||
showLogoutDialog.value = true
|
||||
}
|
||||
RowDivider()
|
||||
|
||||
Text(
|
||||
text = version,
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier
|
||||
.padding(15.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (showLogoutDialog.value) {
|
||||
|
|
|
|||
|
|
@ -34,12 +34,12 @@ val md_theme_light_outlineVariant = Color(0xFFD3C4B4)
|
|||
val md_theme_light_scrim = Color(0xFF000000)
|
||||
|
||||
val md_theme_dark_primary = Color(0xFFEFC125)
|
||||
val md_theme_dark_onPrimary = Color(0xFF3D2F00)
|
||||
val md_theme_dark_primaryContainer = Color(0xFF633F00)
|
||||
val md_theme_dark_onPrimary = Color(0xFF212121)
|
||||
val md_theme_dark_primaryContainer = Color(0xFF212121)
|
||||
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)
|
||||
val md_theme_dark_secondary = Color(0xFFDDC2A1)
|
||||
val md_theme_dark_onSecondary = Color(0xFF3E2D16)
|
||||
val md_theme_dark_secondaryContainer = Color(0xFF56442A)
|
||||
val md_theme_dark_onSecondary = Color(0xFF212121)
|
||||
val md_theme_dark_secondaryContainer = Color(0xFF283237)
|
||||
val md_theme_dark_onSecondaryContainer = Color(0xFFFBDEBC)
|
||||
val md_theme_dark_tertiary = Color(0xFFB8CEA1)
|
||||
val md_theme_dark_onTertiary = Color(0xFF243515)
|
||||
|
|
@ -49,20 +49,20 @@ val md_theme_dark_error = Color(0xFFFFB4AB)
|
|||
val md_theme_dark_errorContainer = Color(0xFF93000A)
|
||||
val md_theme_dark_onError = Color(0xFF690005)
|
||||
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
|
||||
val md_theme_dark_background = Color(0xFF1F1B16)
|
||||
val md_theme_dark_background = Color(0xFF262626)
|
||||
val md_theme_dark_onBackground = Color(0xFFEAE1D9)
|
||||
val md_theme_dark_surface = Color(0xFF1F1B16)
|
||||
val md_theme_dark_onSurface = Color(0xFFEAE1D9)
|
||||
val md_theme_dark_surfaceVariant = Color(0xFF4F4539)
|
||||
val md_theme_dark_surfaceVariant = Color(0xFF212121)
|
||||
val md_theme_dark_onSurfaceVariant = Color(0xFFD3C4B4)
|
||||
val md_theme_dark_outline = Color(0xFF9C8F80)
|
||||
val md_theme_dark_inverseOnSurface = Color(0xFF1F1B16)
|
||||
val md_theme_dark_inverseSurface = Color(0xFFEAE1D9)
|
||||
val md_theme_dark_inversePrimary = Color(0xFF825500)
|
||||
val md_theme_dark_inversePrimary = Color(0xFF212121)
|
||||
val md_theme_dark_shadow = Color(0xFF000000)
|
||||
val md_theme_dark_surfaceTint = Color(0xFFFFB951)
|
||||
val md_theme_dark_outlineVariant = Color(0xFF4F4539)
|
||||
val md_theme_dark_outlineVariant = Color(0xFF424242)
|
||||
val md_theme_dark_scrim = Color(0xFF000000)
|
||||
|
||||
|
||||
val seed = Color(0xFF825500)
|
||||
// val seed = Color(0xFF825500)
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
package app.omnivore.omnivore
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -83,8 +83,8 @@ const App = () => {
|
|||
articleMutations={{
|
||||
createHighlightMutation: (input) =>
|
||||
mutation('createHighlight', input),
|
||||
deleteHighlightMutation: (highlightId) =>
|
||||
mutation('deleteHighlight', { highlightId }),
|
||||
deleteHighlightMutation: (libraryItemId, highlightId) =>
|
||||
mutation('deleteHighlight', { libraryItemId, highlightId }),
|
||||
mergeHighlightMutation: (input) =>
|
||||
mutation('mergeHighlight', input),
|
||||
updateHighlightMutation: (input) =>
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element {
|
|||
;(async () => {
|
||||
const success = await updateHighlightMutation({
|
||||
annotation: text,
|
||||
libraryItemId: props.targetId,
|
||||
highlightId: props.highlight?.id,
|
||||
})
|
||||
if (success) {
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element {
|
|||
;(async () => {
|
||||
const success = await updateHighlightMutation({
|
||||
annotation: text,
|
||||
libraryItemId: props.targetId,
|
||||
highlightId: props.highlight?.id,
|
||||
})
|
||||
if (success) {
|
||||
|
|
|
|||
|
|
@ -310,6 +310,7 @@ export default function EpubContainer(props: EpubContainerProps): JSX.Element {
|
|||
{noteTarget && (
|
||||
<HighlightNoteModal
|
||||
highlight={noteTarget}
|
||||
libraryItemId={props.article.id}
|
||||
author={props.article.author ?? ''}
|
||||
title={props.article.title}
|
||||
onUpdate={(highlight: Highlight) => {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ type HighlightNoteModalProps = {
|
|||
author: string
|
||||
title: string
|
||||
highlight?: Highlight
|
||||
libraryItemId: string
|
||||
onUpdate: (updatedHighlight: Highlight) => void
|
||||
onOpenChange: (open: boolean) => void
|
||||
createHighlightForNote?: (note?: string) => Promise<Highlight | undefined>
|
||||
|
|
@ -38,6 +39,7 @@ export function HighlightNoteModal(
|
|||
const saveNoteChanges = useCallback(async () => {
|
||||
if (noteContent != props.highlight?.annotation && props.highlight?.id) {
|
||||
const result = await updateHighlightMutation({
|
||||
libraryItemId: props.libraryItemId,
|
||||
highlightId: props.highlight?.id,
|
||||
annotation: noteContent,
|
||||
color: props.highlight?.color,
|
||||
|
|
|
|||
|
|
@ -187,7 +187,10 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
}
|
||||
|
||||
const didDeleteHighlight =
|
||||
await props.articleMutations.deleteHighlightMutation(highlightId)
|
||||
await props.articleMutations.deleteHighlightMutation(
|
||||
props.articleId,
|
||||
highlightId
|
||||
)
|
||||
|
||||
if (didDeleteHighlight) {
|
||||
removeHighlights(
|
||||
|
|
@ -222,6 +225,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
updateHighlightsCallback(highlight)
|
||||
;(async () => {
|
||||
const update = await props.articleMutations.updateHighlightMutation({
|
||||
libraryItemId: props.articleId,
|
||||
highlightId: highlight.id,
|
||||
color: color,
|
||||
})
|
||||
|
|
@ -705,6 +709,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
const annotation = event.annotation ?? ''
|
||||
|
||||
const result = await props.articleMutations.updateHighlightMutation({
|
||||
libraryItemId: props.articleId,
|
||||
highlightId: focusedHighlight.id,
|
||||
annotation: event.annotation ?? '',
|
||||
})
|
||||
|
|
@ -788,6 +793,7 @@ export function HighlightsLayer(props: HighlightsLayerProps): JSX.Element {
|
|||
highlight={highlightModalAction.highlight}
|
||||
author={props.articleAuthor}
|
||||
title={props.articleTitle}
|
||||
libraryItemId={props.articleId}
|
||||
onUpdate={updateHighlightsCallback}
|
||||
onOpenChange={() =>
|
||||
setHighlightModalAction({ highlightModalAction: 'none' })
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
|
|||
(note: Highlight, text: string, startTime: Date) => {
|
||||
;(async () => {
|
||||
const result = await updateHighlightMutation({
|
||||
libraryItemId: props.item.id,
|
||||
highlightId: note.id,
|
||||
annotation: text,
|
||||
})
|
||||
|
|
@ -195,7 +196,7 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
|
|||
highlights
|
||||
?.filter((h) => h.type === 'NOTE')
|
||||
.forEach(async (h) => {
|
||||
const result = await deleteHighlightMutation(h.id)
|
||||
const result = await deleteHighlightMutation(props.item.id, h.id)
|
||||
if (!result) {
|
||||
showErrorToast('Error deleting note')
|
||||
}
|
||||
|
|
@ -325,6 +326,7 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
|
|||
;(async () => {
|
||||
const highlightId = showConfirmDeleteHighlightId
|
||||
const success = await deleteHighlightMutation(
|
||||
props.item.id,
|
||||
showConfirmDeleteHighlightId
|
||||
)
|
||||
mutate()
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ export default function PdfArticleContainer(
|
|||
.delete(annotation)
|
||||
.then(() => {
|
||||
if (annotationId) {
|
||||
return deleteHighlightMutation(annotationId)
|
||||
return deleteHighlightMutation(props.article.id, annotationId)
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
|
|
@ -229,7 +229,7 @@ export default function PdfArticleContainer(
|
|||
}
|
||||
const annotationId = annotationOmnivoreId(annotation)
|
||||
if (annotationId) {
|
||||
await deleteHighlightMutation(annotationId)
|
||||
await deleteHighlightMutation(props.article.id, annotationId)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -512,7 +512,7 @@ export default function PdfArticleContainer(
|
|||
const storedId = annotationOmnivoreId(annotation)
|
||||
if (storedId == annotationId) {
|
||||
await instance.delete(annotation)
|
||||
await deleteHighlightMutation(annotationId)
|
||||
await deleteHighlightMutation(props.article.id, annotationId)
|
||||
|
||||
const highlightIdx = highlightsRef.current.findIndex((value) => {
|
||||
return value.id == annotationId
|
||||
|
|
@ -576,6 +576,7 @@ export default function PdfArticleContainer(
|
|||
{noteTarget && (
|
||||
<HighlightNoteModal
|
||||
highlight={noteTarget}
|
||||
libraryItemId={props.article.id}
|
||||
author={props.article.author ?? ''}
|
||||
title={props.article.title}
|
||||
onUpdate={(highlight: Highlight) => {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ export type ArticleMutations = {
|
|||
createHighlightMutation: (
|
||||
input: CreateHighlightInput
|
||||
) => Promise<Highlight | undefined>
|
||||
deleteHighlightMutation: (highlightId: string) => Promise<boolean>
|
||||
deleteHighlightMutation: (
|
||||
libraryItemId: string,
|
||||
highlightId: string
|
||||
) => Promise<boolean>
|
||||
mergeHighlightMutation: (
|
||||
input: MergeHighlightInput
|
||||
) => Promise<Highlight | undefined>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { gql } from 'graphql-request'
|
|||
import { gqlFetcher } from '../networkHelpers'
|
||||
|
||||
export async function deleteHighlightMutation(
|
||||
libraryItemId: string,
|
||||
highlightId: string
|
||||
): Promise<boolean> {
|
||||
const mutation = gql`
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { gqlFetcher } from '../networkHelpers'
|
|||
|
||||
export type UpdateHighlightInput = {
|
||||
highlightId: string
|
||||
libraryItemId?: string
|
||||
annotation?: string
|
||||
sharedAt?: string
|
||||
color?: string
|
||||
|
|
|
|||
Loading…
Reference in a new issue