refactor packages

This commit is contained in:
Stefano Sansone
2024-02-07 17:33:28 +00:00
parent a4fe5320a8
commit 3835b6ac2e
118 changed files with 1906 additions and 1621 deletions

View File

@ -67,45 +67,52 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = "1.8"
}
buildFeatures {
compose true
buildConfig true
}
composeOptions {
kotlinCompilerExtensionVersion = '1.3.1'
kotlinCompilerExtensionVersion = "1.5.8"
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
namespace 'app.omnivore.omnivore'
namespace "app.omnivore.omnivore"
}
dependencies {
def nav_version = "2.5.3"
def nav_version = ""
implementation("androidx.core:core-ktx:1.12.0")
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation 'androidx.activity:activity-compose:1.8.2'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.gms:play-services-base:18.1.0'
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.gms:play-services-base:18.3.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation "androidx.navigation:navigation-compose:$nav_version"
// Compose
def composeBom = platform('androidx.compose:compose-bom:2024.01.00')
implementation(composeBom)
androidTestImplementation(composeBom)
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui:")
implementation("androidx.compose.material:material")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material:material-icons-extended")
implementation("androidx.activity:activity-compose:1.8.2")
implementation("androidx.navigation:navigation-compose:2.7.6")
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
// Testing
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
// Jetpack Lifecycle deps
// ViewModel
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
@ -114,7 +121,7 @@ dependencies {
// LiveData
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
implementation("androidx.compose.runtime:runtime-livedata:1.3.2")
implementation("androidx.compose.runtime:runtime-livedata:1.6.0")
// Saved state module for ViewModel
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version")
@ -126,8 +133,8 @@ dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
// coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
implementation "androidx.security:security-crypto:1.0.0"
implementation "androidx.datastore:datastore-preferences:1.0.0"
@ -138,29 +145,28 @@ dependencies {
implementation("com.apollographql.apollo3:apollo-runtime:3.8.2")
implementation("androidx.compose.material3:material3:1.1.2")
implementation 'androidx.compose.material3:material3-window-size-class:1.1.2'
implementation 'com.google.android.gms:play-services-auth:20.4.0'
implementation "com.google.accompanist:accompanist-systemuicontroller:0.25.1"
implementation "com.google.accompanist:accompanist-flowlayout:0.25.1"
implementation 'io.coil-kt:coil-compose:2.3.0'
implementation("com.google.android.gms:play-services-auth:20.7.0")
implementation("com.google.accompanist:accompanist-systemuicontroller:0.34.0")
implementation("com.google.accompanist:accompanist-flowlayout:0.32.0")
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'com.pspdfkit:pspdfkit:8.9.1'
implementation("io.coil-kt:coil-compose:2.5.0")
implementation 'com.posthog.android:posthog:2.0.3'
implementation 'io.intercom.android:intercom-sdk:15.1.0'
implementation("com.google.code.gson:gson:2.10")
implementation("com.pspdfkit:pspdfkit:8.9.1")
implementation("com.posthog.android:posthog:2.0.3")
implementation("io.intercom.android:intercom-sdk:15.1.0")
// Room Deps
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation("androidx.room:room-runtime:$room_version")
implementation("androidx.room:room-ktx:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
kapt("androidx.room:room-compiler:$room_version")
implementation 'com.github.jeziellago:compose-markdown:0.3.3'
implementation "io.github.dokar3:chiptextfield:0.4.7"
implementation("com.github.jeziellago:compose-markdown:0.3.3")
implementation("io.github.dokar3:chiptextfield-m3:0.6.5")
}
apollo {

View File

@ -31,7 +31,7 @@
</activity>
<activity
android:name=".ui.save.SaveSheetActivity"
android:name=".feature.save.SaveSheetActivity"
android:exported="true"
android:theme="@style/Theme.AppCompat.Translucent">
<intent-filter>
@ -47,12 +47,12 @@
android:windowSoftInputMode="adjustNothing" />
<activity
android:name=".ui.reader.PDFReaderActivity"
android:name=".feature.reader.PDFReaderActivity"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:windowSoftInputMode="adjustNothing" />
<activity
android:name=".ui.reader.WebReaderLoadingContainerActivity"
android:name=".feature.reader.WebReaderLoadingContainerActivity"
android:exported="true"
android:theme="@style/Theme.Omnivore"/>

View File

@ -1,45 +0,0 @@
package app.omnivore.omnivore
import android.content.Context
import com.posthog.android.PostHog
import com.posthog.android.Properties
import io.intercom.android.sdk.Intercom
import io.intercom.android.sdk.identity.Registration
import org.json.JSONObject
import javax.inject.Inject
class EventTracker @Inject constructor(val app: Context) {
private val posthog: PostHog
init {
val posthogClientKey = app.getString(R.string.posthog_client_key)
val posthogInstanceAddress = app.getString(R.string.posthog_instance_address)
posthog = PostHog.Builder(app, posthogClientKey, posthogInstanceAddress)
.captureApplicationLifecycleEvents()
.collectDeviceId(false)
.build()
PostHog.setSingletonInstance(posthog)
}
fun registerUser(userID: String, intercomHash: String?, isDebug: Boolean) {
posthog.identify(userID)
if (!isDebug) {
Intercom.client().loginIdentifiedUser(Registration.create().withUserId(userID))
intercomHash?.let { intercomHash ->
Intercom.client().setUserHash(intercomHash)
}
}
}
fun track(eventName: String, properties: Properties = Properties()) {
posthog.capture(eventName, properties)
}
fun logout() {
posthog.reset()
}
}

View File

@ -12,15 +12,15 @@ import androidx.compose.ui.Modifier
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import app.omnivore.omnivore.ui.auth.LoginViewModel
import app.omnivore.omnivore.ui.components.LabelsViewModel
import app.omnivore.omnivore.ui.editinfo.EditInfoViewModel
import app.omnivore.omnivore.ui.library.LibraryViewModel
import app.omnivore.omnivore.ui.library.SearchViewModel
import app.omnivore.omnivore.ui.root.RootView
import app.omnivore.omnivore.ui.save.SaveViewModel
import app.omnivore.omnivore.ui.settings.SettingsViewModel
import app.omnivore.omnivore.ui.theme.OmnivoreTheme
import app.omnivore.omnivore.feature.auth.LoginViewModel
import app.omnivore.omnivore.feature.components.LabelsViewModel
import app.omnivore.omnivore.feature.editinfo.EditInfoViewModel
import app.omnivore.omnivore.feature.library.LibraryViewModel
import app.omnivore.omnivore.feature.library.SearchViewModel
import app.omnivore.omnivore.feature.root.RootView
import app.omnivore.omnivore.feature.save.SaveViewModel
import app.omnivore.omnivore.feature.settings.SettingsViewModel
import app.omnivore.omnivore.feature.theme.OmnivoreTheme
import com.pspdfkit.PSPDFKit
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.DelicateCoroutinesApi

View File

@ -1,11 +0,0 @@
package app.omnivore.omnivore
sealed class Routes(val route: String) {
object Library : Routes("Library")
object Settings: Routes("Settings")
object Search: Routes("Search")
object Documentation: Routes("Documentation")
object PrivacyPolicy: Routes("PrivacyPolicy")
object TermsAndConditions: Routes("TermsAndConditions")
object Notebook: Routes("Notebook")
}

View File

@ -0,0 +1,45 @@
package app.omnivore.omnivore.core.analytics
import android.content.Context
import app.omnivore.omnivore.R
import com.posthog.android.PostHog
import com.posthog.android.Properties
import io.intercom.android.sdk.Intercom
import io.intercom.android.sdk.identity.Registration
import javax.inject.Inject
class EventTracker @Inject constructor(val app: Context) {
private val posthog: PostHog
init {
val posthogClientKey = app.getString(R.string.posthog_client_key)
val posthogInstanceAddress = app.getString(R.string.posthog_instance_address)
posthog = PostHog.Builder(app, posthogClientKey, posthogInstanceAddress)
.captureApplicationLifecycleEvents()
.collectDeviceId(false)
.build()
PostHog.setSingletonInstance(posthog)
}
fun registerUser(userID: String, intercomHash: String?, isDebug: Boolean) {
posthog.identify(userID)
if (!isDebug) {
Intercom.client().loginIdentifiedUser(Registration.create().withUserId(userID))
intercomHash?.let { intercomHash ->
Intercom.client().setUserHash(intercomHash)
}
}
}
fun track(eventName: String, properties: Properties = Properties()) {
posthog.capture(eventName, properties)
}
fun logout() {
posthog.reset()
}
}

View File

@ -1,11 +1,11 @@
package app.omnivore.omnivore.dataService
package app.omnivore.omnivore.core.data
import android.content.Context
import androidx.room.Room
import app.omnivore.omnivore.network.*
import app.omnivore.omnivore.persistence.AppDatabase
import app.omnivore.omnivore.persistence.entities.Highlight
import app.omnivore.omnivore.persistence.entities.SavedItem
import app.omnivore.omnivore.core.network.Networker
import app.omnivore.omnivore.core.database.AppDatabase
import app.omnivore.omnivore.core.database.entities.Highlight
import app.omnivore.omnivore.core.database.entities.SavedItem
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import javax.inject.Inject

View File

@ -1,11 +1,14 @@
package app.omnivore.omnivore.dataService
package app.omnivore.omnivore.core.data
import android.util.Log
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.network.*
import app.omnivore.omnivore.persistence.entities.Highlight
import app.omnivore.omnivore.persistence.entities.SavedItemAndHighlightCrossRef
import app.omnivore.omnivore.persistence.entities.saveHighlightChange
import app.omnivore.omnivore.core.network.CreateHighlightParams
import app.omnivore.omnivore.core.network.DeleteHighlightParams
import app.omnivore.omnivore.core.network.MergeHighlightsParams
import app.omnivore.omnivore.core.network.UpdateHighlightParams
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.database.entities.Highlight
import app.omnivore.omnivore.core.database.entities.SavedItemAndHighlightCrossRef
import app.omnivore.omnivore.core.database.entities.saveHighlightChange
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -49,7 +52,7 @@ suspend fun DataService.createWebHighlight(jsonString: String, colorName: String
}
suspend fun DataService.createNoteHighlight(savedItemId: String, note: String): String {
val shortId = NanoId.generate(size=14)
val shortId = NanoId.generate(size = 14)
val createHighlightId = UUID.randomUUID().toString()
withContext(Dispatchers.IO) {

View File

@ -1,9 +1,14 @@
package app.omnivore.omnivore.dataService
package app.omnivore.omnivore.core.data
import android.util.Log
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.network.*
import app.omnivore.omnivore.persistence.entities.*
import app.omnivore.omnivore.core.database.entities.Highlight
import app.omnivore.omnivore.core.database.entities.SavedItem
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.core.network.savedItem
import app.omnivore.omnivore.core.network.savedItemUpdates
import app.omnivore.omnivore.core.network.search
import app.omnivore.omnivore.core.model.ServerSyncStatus
suspend fun DataService.librarySearch(cursor: String?, query: String): SearchResult {
val searchResult = networker.search(cursor = cursor, limit = 10, query = query)

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.dataService
package app.omnivore.omnivore.core.data
import java.security.SecureRandom

View File

@ -1,8 +1,8 @@
package app.omnivore.omnivore.dataService
package app.omnivore.omnivore.core.data
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.network.ReadingProgressParams
import app.omnivore.omnivore.network.updateReadingProgress
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.network.ReadingProgressParams
import app.omnivore.omnivore.core.network.updateReadingProgress
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

View File

@ -1,6 +1,6 @@
package app.omnivore.omnivore.dataService
package app.omnivore.omnivore.core.data
import app.omnivore.omnivore.network.savedItemLabels
import app.omnivore.omnivore.core.network.savedItemLabels
suspend fun DataService.syncLabels() {
val fetchedLabels = networker.savedItemLabels()

View File

@ -1,9 +1,9 @@
package app.omnivore.omnivore.dataService
package app.omnivore.omnivore.core.data
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.network.archiveSavedItem
import app.omnivore.omnivore.network.deleteSavedItem
import app.omnivore.omnivore.network.unarchiveSavedItem
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.network.archiveSavedItem
import app.omnivore.omnivore.core.network.deleteSavedItem
import app.omnivore.omnivore.core.network.unarchiveSavedItem
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -54,3 +54,4 @@ suspend fun DataService.unarchiveSavedItem(itemID: String) {
}
}
}

View File

@ -1,15 +1,22 @@
package app.omnivore.omnivore.dataService
package app.omnivore.omnivore.core.data
import android.util.Log
import app.omnivore.omnivore.core.network.ReadingProgressParams
import app.omnivore.omnivore.core.network.createHighlight
import app.omnivore.omnivore.core.network.deleteHighlights
import app.omnivore.omnivore.core.network.deleteSavedItem
import app.omnivore.omnivore.core.network.mergeHighlights
import app.omnivore.omnivore.core.network.updateArchiveStatusSavedItem
import app.omnivore.omnivore.core.network.updateHighlight
import app.omnivore.omnivore.core.network.updateReadingProgress
import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput
import app.omnivore.omnivore.graphql.generated.type.HighlightType
import app.omnivore.omnivore.graphql.generated.type.MergeHighlightInput
import app.omnivore.omnivore.graphql.generated.type.UpdateHighlightInput
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.network.*
import app.omnivore.omnivore.persistence.entities.HighlightChange
import app.omnivore.omnivore.persistence.entities.SavedItem
import app.omnivore.omnivore.persistence.entities.highlightChangeToHighlight
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.database.entities.HighlightChange
import app.omnivore.omnivore.core.database.entities.SavedItem
import app.omnivore.omnivore.core.database.entities.highlightChangeToHighlight
import com.apollographql.apollo3.api.Optional
import kotlinx.coroutines.delay

View File

@ -0,0 +1,42 @@
package app.omnivore.omnivore.core.database
import androidx.room.Database
import androidx.room.RoomDatabase
import app.omnivore.omnivore.core.database.entities.Highlight
import app.omnivore.omnivore.core.database.entities.HighlightChange
import app.omnivore.omnivore.core.database.entities.HighlightChangesDao
import app.omnivore.omnivore.core.database.entities.HighlightDao
import app.omnivore.omnivore.core.database.entities.SavedItem
import app.omnivore.omnivore.core.database.entities.SavedItemAndHighlightCrossRef
import app.omnivore.omnivore.core.database.entities.SavedItemAndHighlightCrossRefDao
import app.omnivore.omnivore.core.database.entities.SavedItemAndSavedItemLabelCrossRef
import app.omnivore.omnivore.core.database.entities.SavedItemAndSavedItemLabelCrossRefDao
import app.omnivore.omnivore.core.database.entities.SavedItemDao
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.core.database.entities.SavedItemLabelDao
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlightsDao
import app.omnivore.omnivore.core.database.entities.Viewer
import app.omnivore.omnivore.core.database.entities.ViewerDao
@Database(
entities = [
Viewer::class,
SavedItem::class,
SavedItemLabel::class,
Highlight::class,
HighlightChange::class,
SavedItemAndSavedItemLabelCrossRef::class,
SavedItemAndHighlightCrossRef::class
],
version = 24
)
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
abstract fun savedItemAndHighlightCrossRefDao(): SavedItemAndHighlightCrossRefDao
}

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.persistence
package app.omnivore.omnivore.core.database
import androidx.room.Delete
import androidx.room.Insert

View File

@ -1,7 +1,7 @@
package app.omnivore.omnivore.persistence.entities
package app.omnivore.omnivore.core.database.entities
import androidx.room.*
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.core.model.ServerSyncStatus
import com.google.gson.annotations.SerializedName

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.persistence.entities
package app.omnivore.omnivore.core.database.entities
import android.util.Log
import androidx.room.Dao
@ -9,11 +9,9 @@ import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.core.model.ServerSyncStatus
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import kotlinx.serialization.json.Json
@Entity
@TypeConverters(StringListTypeConverter::class)

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.persistence.entities
package app.omnivore.omnivore.core.database.entities
import androidx.room.Entity
import androidx.room.PrimaryKey

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.persistence.entities
package app.omnivore.omnivore.core.database.entities
import androidx.room.Entity
import androidx.room.PrimaryKey

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.persistence.entities
package app.omnivore.omnivore.core.database.entities
import androidx.room.Entity
import androidx.room.PrimaryKey

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.persistence.entities
package app.omnivore.omnivore.core.database.entities
import androidx.room.Entity
import androidx.room.PrimaryKey

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.persistence.entities
package app.omnivore.omnivore.core.database.entities
import androidx.core.net.toUri
import androidx.lifecycle.LiveData
@ -225,7 +225,7 @@ interface SavedItemDao {
excludedLabels: List<String>,
allowedContentReaders: List<String>
): LiveData<List<SavedItemWithLabelsAndHighlights>> {
val result = _filteredLibraryData(
return _filteredLibraryData(
allowedArchiveStates = allowedArchiveStates,
sortKey = sortKey,
hasRequiredLabels = requiredLabels.size,
@ -234,7 +234,6 @@ interface SavedItemDao {
excludedLabels = excludedLabels,
allowedContentReaders = allowedContentReaders
)
return result
}
}

View File

@ -1,8 +1,8 @@
package app.omnivore.omnivore.persistence.entities
package app.omnivore.omnivore.core.database.entities
import androidx.lifecycle.LiveData
import androidx.room.*
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.core.model.ServerSyncStatus
@Entity
data class SavedItemLabel(

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.persistence.entities
package app.omnivore.omnivore.core.database.entities
import androidx.room.Entity
import androidx.room.PrimaryKey

View File

@ -1,7 +1,6 @@
package app.omnivore.omnivore.persistence.entities
package app.omnivore.omnivore.core.database.entities
import androidx.room.*
import app.omnivore.omnivore.persistence.BaseDao
@Entity
data class Viewer(

View File

@ -1,9 +1,11 @@
package app.omnivore.omnivore
package app.omnivore.omnivore.core.datastore
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.*
import androidx.datastore.preferences.preferencesDataStore
import app.omnivore.omnivore.utils.Constants
import app.omnivore.omnivore.utils.DatastoreKeys
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map

View File

@ -0,0 +1,10 @@
package app.omnivore.omnivore.core.model
enum class ServerSyncStatus(val rawValue: Int) {
IS_SYNCED(0),
IS_SYNCING(1),
NEEDS_DELETION(2),
NEEDS_CREATION(3),
NEEDS_UPDATE(4),
NEEDS_MERGE(5)
}

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.network
package app.omnivore.omnivore.core.network
import android.util.Log
import app.omnivore.omnivore.graphql.generated.CreateHighlightMutation
@ -10,7 +10,7 @@ import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput
import app.omnivore.omnivore.graphql.generated.type.HighlightType
import app.omnivore.omnivore.graphql.generated.type.MergeHighlightInput
import app.omnivore.omnivore.graphql.generated.type.UpdateHighlightInput
import app.omnivore.omnivore.persistence.entities.Highlight
import app.omnivore.omnivore.core.database.entities.Highlight
import com.apollographql.apollo3.api.Optional
import com.google.gson.Gson

View File

@ -1,8 +1,8 @@
package app.omnivore.omnivore.network
package app.omnivore.omnivore.core.network
import app.omnivore.omnivore.Constants
import app.omnivore.omnivore.DatastoreKeys
import app.omnivore.omnivore.DatastoreRepository
import app.omnivore.omnivore.utils.Constants
import app.omnivore.omnivore.utils.DatastoreKeys
import app.omnivore.omnivore.core.datastore.DatastoreRepository
import com.apollographql.apollo3.ApolloClient
import javax.inject.Inject

View File

@ -1,6 +1,5 @@
package app.omnivore.omnivore
package app.omnivore.omnivore.core.network
import app.omnivore.omnivore.network.Networker
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.network
package app.omnivore.omnivore.core.network
import app.omnivore.omnivore.graphql.generated.SaveArticleReadingProgressMutation
import app.omnivore.omnivore.graphql.generated.type.SaveArticleReadingProgressInput

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.network
package app.omnivore.omnivore.core.network
import app.omnivore.omnivore.graphql.generated.CreateLabelMutation
import app.omnivore.omnivore.graphql.generated.SetLabelsMutation

View File

@ -1,7 +1,7 @@
package app.omnivore.omnivore.network
package app.omnivore.omnivore.core.network
import app.omnivore.omnivore.graphql.generated.GetLabelsQuery
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
suspend fun Networker.savedItemLabels(): List<SavedItemLabel> {

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.network
package app.omnivore.omnivore.core.network
import android.net.Uri
import androidx.compose.ui.text.intl.Locale

View File

@ -1,11 +1,11 @@
package app.omnivore.omnivore.network
package app.omnivore.omnivore.core.network
import android.util.Log
import app.omnivore.omnivore.graphql.generated.GetArticleQuery
import app.omnivore.omnivore.graphql.generated.type.ContentReader
import app.omnivore.omnivore.persistence.entities.SavedItem
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.persistence.entities.Highlight
import app.omnivore.omnivore.core.database.entities.SavedItem
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.core.database.entities.Highlight
import java.io.File
import java.net.URL
import java.nio.file.Files

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.network
package app.omnivore.omnivore.core.network
import app.omnivore.omnivore.graphql.generated.UpdatesSinceQuery
import app.omnivore.omnivore.graphql.generated.type.UpdateReason

View File

@ -1,8 +1,10 @@
package app.omnivore.omnivore.network
package app.omnivore.omnivore.core.network
import app.omnivore.omnivore.core.database.entities.Highlight
import app.omnivore.omnivore.core.database.entities.SavedItem
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.graphql.generated.SearchQuery
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.persistence.entities.*
import app.omnivore.omnivore.core.model.ServerSyncStatus
import com.apollographql.apollo3.api.Optional
data class LibrarySearchQueryResponse(

View File

@ -1,7 +1,7 @@
package app.omnivore.omnivore.network
package app.omnivore.omnivore.core.network
import app.omnivore.omnivore.graphql.generated.TypeaheadSearchQuery
import app.omnivore.omnivore.persistence.entities.TypeaheadCardData
import app.omnivore.omnivore.core.database.entities.TypeaheadCardData
data class SearchQueryResponse(
val cursor: String?,

View File

@ -1,7 +1,7 @@
package app.omnivore.omnivore.network
package app.omnivore.omnivore.core.network
import app.omnivore.omnivore.graphql.generated.ViewerQuery
import app.omnivore.omnivore.persistence.entities.Viewer
import app.omnivore.omnivore.core.database.entities.Viewer
suspend fun Networker.viewer(): Viewer? {
try {

View File

@ -1,8 +1,11 @@
package app.omnivore.omnivore
package app.omnivore.omnivore.di
import android.content.Context
import app.omnivore.omnivore.dataService.DataService
import app.omnivore.omnivore.network.Networker
import app.omnivore.omnivore.core.datastore.DatastoreRepository
import app.omnivore.omnivore.core.analytics.EventTracker
import app.omnivore.omnivore.core.datastore.OmnivoreDatastore
import app.omnivore.omnivore.core.data.DataService
import app.omnivore.omnivore.core.network.Networker
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn

View File

@ -1,12 +1,12 @@
package app.omnivore.omnivore.ui
package app.omnivore.omnivore.feature
import app.omnivore.omnivore.dataService.DataService
import app.omnivore.omnivore.core.data.DataService
import app.omnivore.omnivore.graphql.generated.type.CreateLabelInput
import app.omnivore.omnivore.graphql.generated.type.SetLabelsInput
import app.omnivore.omnivore.network.Networker
import app.omnivore.omnivore.network.updateLabelsForSavedItem
import app.omnivore.omnivore.persistence.entities.SavedItemAndSavedItemLabelCrossRef
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.core.network.Networker
import app.omnivore.omnivore.core.network.updateLabelsForSavedItem
import app.omnivore.omnivore.core.database.entities.SavedItemAndSavedItemLabelCrossRef
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import com.apollographql.apollo3.api.Optional

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui
package app.omnivore.omnivore.feature
import android.content.Context
import androidx.annotation.StringRes

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.auth
package app.omnivore.omnivore.feature.auth
import android.annotation.SuppressLint
import android.net.Uri
@ -17,7 +17,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
import app.omnivore.omnivore.AppleConstants
import app.omnivore.omnivore.utils.AppleConstants
import app.omnivore.omnivore.R
import java.net.URLEncoder
import java.util.*

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.auth
package app.omnivore.omnivore.feature.auth
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.auth
package app.omnivore.omnivore.feature.auth
import android.annotation.SuppressLint
import android.widget.Toast

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.auth
package app.omnivore.omnivore.feature.auth
import android.annotation.SuppressLint
import android.widget.Toast
@ -27,7 +27,7 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import app.omnivore.omnivore.BuildConfig
import app.omnivore.omnivore.R
import app.omnivore.omnivore.ui.auth.AuthUtils.autofill
import app.omnivore.omnivore.feature.auth.AuthUtils.autofill
@SuppressLint("CoroutineCreationDuringComposition")
@Composable

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.auth
package app.omnivore.omnivore.feature.auth
import android.annotation.SuppressLint
import android.widget.Toast
@ -29,7 +29,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import app.omnivore.omnivore.R
import app.omnivore.omnivore.ui.auth.AuthUtils.autofill
import app.omnivore.omnivore.feature.auth.AuthUtils.autofill
@Composable
fun EmailSignUpView(viewModel: LoginViewModel) {

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.auth
package app.omnivore.omnivore.feature.auth
import android.app.Activity
import androidx.activity.compose.rememberLauncherForActivityResult

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.auth
package app.omnivore.omnivore.feature.auth
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.auth
package app.omnivore.omnivore.feature.auth
import android.content.Context
import android.widget.Toast
@ -7,11 +7,26 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.*
import app.omnivore.omnivore.*
import app.omnivore.omnivore.dataService.DataService
import app.omnivore.omnivore.core.analytics.EventTracker
import app.omnivore.omnivore.core.data.DataService
import app.omnivore.omnivore.core.datastore.DatastoreRepository
import app.omnivore.omnivore.core.network.AuthProviderLoginSubmit
import app.omnivore.omnivore.core.network.CreateAccountParams
import app.omnivore.omnivore.core.network.CreateAccountSubmit
import app.omnivore.omnivore.core.network.CreateEmailAccountSubmit
import app.omnivore.omnivore.core.network.EmailLoginCredentials
import app.omnivore.omnivore.core.network.EmailLoginSubmit
import app.omnivore.omnivore.core.network.EmailSignUpParams
import app.omnivore.omnivore.graphql.generated.ValidateUsernameQuery
import app.omnivore.omnivore.network.Networker
import app.omnivore.omnivore.network.viewer
import app.omnivore.omnivore.ui.ResourceProvider
import app.omnivore.omnivore.core.network.Networker
import app.omnivore.omnivore.core.network.PendingUserSubmit
import app.omnivore.omnivore.core.network.RetrofitHelper
import app.omnivore.omnivore.core.network.SignInParams
import app.omnivore.omnivore.core.network.UserProfile
import app.omnivore.omnivore.core.network.viewer
import app.omnivore.omnivore.feature.ResourceProvider
import app.omnivore.omnivore.utils.Constants
import app.omnivore.omnivore.utils.DatastoreKeys
import com.apollographql.apollo3.ApolloClient
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.common.api.ApiException

View File

@ -1,13 +1,8 @@
package app.omnivore.omnivore.ui.auth
package app.omnivore.omnivore.feature.auth
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.view.ViewGroup
import android.webkit.CookieManager
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.ClickableText
@ -19,22 +14,16 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import app.omnivore.omnivore.BuildConfig
import app.omnivore.omnivore.DatastoreKeys
import app.omnivore.omnivore.R
@SuppressLint("CoroutineCreationDuringComposition")

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.auth
package app.omnivore.omnivore.feature.auth
import android.annotation.SuppressLint
import android.content.Intent
@ -21,7 +21,7 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import app.omnivore.omnivore.R
import app.omnivore.omnivore.ui.theme.OmnivoreTheme
import app.omnivore.omnivore.feature.theme.OmnivoreTheme
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.google.android.gms.common.GoogleApiAvailability
import kotlinx.coroutines.launch

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.components
package app.omnivore.omnivore.feature.components
import android.widget.Toast
import androidx.compose.foundation.*
@ -18,14 +18,13 @@ import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import androidx.lifecycle.MutableLiveData
import app.omnivore.omnivore.R
import app.omnivore.omnivore.ui.save.SaveState
import app.omnivore.omnivore.ui.save.SaveViewModel
import app.omnivore.omnivore.feature.save.SaveState
import app.omnivore.omnivore.feature.save.SaveViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.components
package app.omnivore.omnivore.feature.components
import androidx.compose.ui.graphics.Color

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.components
package app.omnivore.omnivore.feature.components
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.components
package app.omnivore.omnivore.feature.components
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.components
package app.omnivore.omnivore.feature.components
import androidx.compose.ui.graphics.Color
enum class HighlightColorPaletteMode(val backgroundColor: Color) {

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.components
package app.omnivore.omnivore.feature.components
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.components
package app.omnivore.omnivore.feature.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background

View File

@ -0,0 +1,47 @@
package app.omnivore.omnivore.feature.components
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Done
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LabelFilterChip(
onClick: () -> Unit,
label: String
) {
var selected by remember { mutableStateOf(false) }
FilterChip(
onClick = {
selected = !selected
onClick()
},
label = {
Text(label)
},
selected = selected,
leadingIcon = if (selected) {
{
Icon(
imageVector = Icons.Filled.Done,
contentDescription = "Done icon",
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
}
} else {
null
},
)
}

View File

@ -0,0 +1,144 @@
package app.omnivore.omnivore.feature.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import app.omnivore.omnivore.R
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LabelsSelectionSheetContent2(
isLibraryMode: Boolean,
labels: List<SavedItemLabel>,
initialSelectedLabels: List<SavedItemLabel>,
labelsViewModel: LabelsViewModel,
onCancel: () -> Unit,
onSave: (List<SavedItemLabel>) -> Unit,
onCreateLabel: (String, String) -> Unit
) {
// Use mutableStateMapOf for more idiomatic management of collection state
val selectedLabels = remember {
mutableStateMapOf<SavedItemLabel, Boolean>().apply {
initialSelectedLabels.forEach {
put(
it,
true
)
}
}
}
var newLabelName by remember { mutableStateOf("") }
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = {
Text(
text = if (isLibraryMode) stringResource(R.string.label_selection_sheet_title) else
stringResource(R.string.label_selection_sheet_title_alt)
)
},
navigationIcon = {
IconButton(onClick = onCancel) {
Icon(Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = "Back")
}
},
actions = {
IconButton(onClick = { onSave(selectedLabels.filter { it.value }.keys.toList()) }) {
Text(
text = if (isLibraryMode)
stringResource(R.string.label_selection_sheet_action_search) else
stringResource(R.string.label_selection_sheet_action_save)
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background
)
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(top = paddingValues.calculateTopPadding())
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start
) {
OutlinedTextField(
value = newLabelName,
onValueChange = { newLabelName = it },
label = { Text("New Label Name") },
singleLine = true,
trailingIcon = {
IconButton(onClick = {
val labelHexValue = "#FFFFFF"
onCreateLabel(newLabelName, labelHexValue)
newLabelName = ""
}) {
Icon(Icons.Filled.Add, contentDescription = "Create Label")
}
},
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
)
Row {
labels.forEach { label ->
FilterChip(
selected = selectedLabels[label] ?: false,
onClick = {
selectedLabels[label] = !(selectedLabels[label] ?: false)
},
label = { Text(label.name) },
leadingIcon = if (selectedLabels[label] == true) {
{
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Selected",
modifier = Modifier.size(FilterChipDefaults.IconSize)
)
}
} else null,
modifier = Modifier.padding(8.dp)
)
}
}
}
}
}

View File

@ -1,6 +1,6 @@
@file:OptIn(ExperimentalMaterialApi::class)
package app.omnivore.omnivore.ui.components
package app.omnivore.omnivore.feature.components
import android.widget.Toast
import androidx.compose.foundation.Canvas
@ -8,7 +8,6 @@ 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
@ -25,17 +24,12 @@ 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
@ -58,20 +52,18 @@ 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
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 app.omnivore.omnivore.core.database.entities.SavedItemLabel
import com.dokar.chiptextfield.Chip
import com.dokar.chiptextfield.ChipTextField
import com.dokar.chiptextfield.ChipTextFieldDefaults
import com.dokar.chiptextfield.ChipTextFieldState
import com.dokar.chiptextfield.m3.ChipTextField
import com.dokar.chiptextfield.m3.ChipTextFieldDefaults
import com.dokar.chiptextfield.rememberChipTextFieldState
import com.google.accompanist.flowlayout.FlowRow

View File

@ -1,8 +1,8 @@
package app.omnivore.omnivore.ui.components
package app.omnivore.omnivore.feature.components
import androidx.lifecycle.*
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.core.model.ServerSyncStatus
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import dagger.hilt.android.lifecycle.HiltViewModel
import java.time.LocalDate
import java.time.ZoneOffset

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.components
package app.omnivore.omnivore.feature.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Row

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.editinfo
package app.omnivore.omnivore.feature.editinfo
import android.widget.Toast
import androidx.compose.foundation.*
@ -11,7 +11,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.editinfo
package app.omnivore.omnivore.feature.editinfo
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -6,14 +6,14 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.omnivore.omnivore.Constants
import app.omnivore.omnivore.DatastoreKeys
import app.omnivore.omnivore.DatastoreRepository
import app.omnivore.omnivore.utils.Constants
import app.omnivore.omnivore.utils.DatastoreKeys
import app.omnivore.omnivore.core.datastore.DatastoreRepository
import app.omnivore.omnivore.R
import app.omnivore.omnivore.dataService.DataService
import app.omnivore.omnivore.core.data.DataService
import app.omnivore.omnivore.graphql.generated.UpdatePageMutation
import app.omnivore.omnivore.graphql.generated.type.UpdatePageInput
import app.omnivore.omnivore.ui.ResourceProvider
import app.omnivore.omnivore.feature.ResourceProvider
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.Optional
import dagger.hilt.android.lifecycle.HiltViewModel

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.library
package app.omnivore.omnivore.feature.library
enum class LibraryBottomSheetState {
HIDDEN,

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.library
package app.omnivore.omnivore.feature.library
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
@ -17,8 +17,8 @@ import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.toLowerCase
import androidx.compose.ui.unit.dp
import app.omnivore.omnivore.R
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.ui.components.LabelChipColors
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.feature.components.LabelChipColors
@Composable
fun LibraryFilterBar(viewModel: LibraryViewModel) {

View File

@ -0,0 +1,234 @@
package app.omnivore.omnivore.feature.library
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.navigation.NavHostController
import app.omnivore.omnivore.R
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LibraryNavigationBar(
savedItemViewModel: SavedItemViewModel,
onSearchClicked: () -> Unit,
onAddLinkClicked: () -> Unit,
onSettingsIconClick: () -> Unit
) {
val actionsMenuItem: SavedItemWithLabelsAndHighlights? by savedItemViewModel.actionsMenuItemLiveData.observeAsState(
null
)
var isMenuExpanded by remember { mutableStateOf(false) }
TopAppBar(
title = {
Text(
if (actionsMenuItem == null)
stringResource(R.string.library_nav_bar_title) else
stringResource(R.string.library_nav_bar_title_alt)
)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = if (actionsMenuItem == null) MaterialTheme.colorScheme.background else MaterialTheme.colorScheme.surfaceVariant
),
navigationIcon = {
if (actionsMenuItem != null) {
IconButton(onClick = {
savedItemViewModel.actionsMenuItemLiveData.postValue(null)
}) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
modifier = Modifier,
contentDescription = "Back"
)
}
}
},
actions = {
actionsMenuItem?.let {
IconButton(onClick = {
savedItemViewModel.handleSavedItemAction(
it.savedItem.savedItemId,
if (it.savedItem.isArchived) SavedItemAction.Unarchive else SavedItemAction.Archive
)
}) {
if (it.savedItem.isArchived) {
Icon(
painter = painterResource(id = R.drawable.unarchive),
contentDescription = null
)
} else {
Icon(
painter = painterResource(id = R.drawable.archive_outline),
contentDescription = null
)
}
}
IconButton(onClick = {
savedItemViewModel.handleSavedItemAction(
it.savedItem.savedItemId,
SavedItemAction.EditInfo
)
}) {
Icon(
Icons.Outlined.Info,
contentDescription = null
)
}
IconButton(onClick = {
savedItemViewModel.handleSavedItemAction(
it.savedItem.savedItemId,
SavedItemAction.EditLabels
)
}) {
Icon(
painter = painterResource(id = R.drawable.tag),
contentDescription = null
)
}
IconButton(onClick = {
savedItemViewModel.handleSavedItemAction(
it.savedItem.savedItemId,
SavedItemAction.Delete
)
}) {
Icon(
imageVector = Icons.Outlined.Delete,
contentDescription = null
)
}
IconButton(onClick = { isMenuExpanded = true } ) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = null
)
if (isMenuExpanded) {
SavedItemLibraryContextMenu(
savedItemViewModel = savedItemViewModel,
savedItem = it.savedItem,
isExpanded = true,
onDismiss = { isMenuExpanded = false },
)
}
}
} ?: run {
IconButton(onClick = onSearchClicked) {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = null
)
}
IconButton(onClick = onAddLinkClicked) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = null
)
}
IconButton(onClick = onSettingsIconClick) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = null
)
}
}
}
)
}
@Composable
fun SearchField(
searchText: String,
onSearch: () -> Unit,
onSearchTextChanged: (String) -> Unit,
navController: NavHostController,
) {
var showClearButton by remember { mutableStateOf(false) }
val focusRequester = remember { FocusRequester() }
TextField(
modifier = Modifier
.fillMaxWidth()
.onFocusChanged { focusState ->
showClearButton = (focusState.isFocused)
}
.focusRequester(focusRequester),
value = searchText,
onValueChange = onSearchTextChanged,
placeholder = {
Text(text = stringResource(R.string.library_nav_bar_field_placeholder_search))
},
leadingIcon = {
IconButton(
onClick = {
onSearchTextChanged("")
navController.popBackStack()
}) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back"
)
}
},
trailingIcon = {
AnimatedVisibility(
visible = showClearButton,
enter = fadeIn(),
exit = fadeOut()
) {
IconButton(onClick = { onSearchTextChanged("") }) {
Icon(
imageVector = Icons.Filled.Close,
contentDescription = null
)
}
}
},
maxLines = 1,
singleLine = true,
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(onSearch = {
onSearch()
}),
)
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.library
package app.omnivore.omnivore.feature.library
import android.content.Intent
import android.util.Log
@ -53,19 +53,19 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import app.omnivore.omnivore.Routes
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.ui.components.AddLinkSheetContent
import app.omnivore.omnivore.ui.components.LabelsSelectionSheetContent
import app.omnivore.omnivore.ui.components.LabelsViewModel
import app.omnivore.omnivore.ui.editinfo.EditInfoSheetContent
import app.omnivore.omnivore.ui.editinfo.EditInfoViewModel
import app.omnivore.omnivore.ui.reader.PDFReaderActivity
import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainerActivity
import app.omnivore.omnivore.ui.save.SaveState
import app.omnivore.omnivore.ui.save.SaveViewModel
import app.omnivore.omnivore.ui.savedItemViews.SavedItemCard
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.feature.components.AddLinkSheetContent
import app.omnivore.omnivore.feature.components.LabelsSelectionSheetContent
import app.omnivore.omnivore.feature.components.LabelsViewModel
import app.omnivore.omnivore.feature.editinfo.EditInfoSheetContent
import app.omnivore.omnivore.feature.editinfo.EditInfoViewModel
import app.omnivore.omnivore.feature.reader.PDFReaderActivity
import app.omnivore.omnivore.feature.reader.WebReaderLoadingContainerActivity
import app.omnivore.omnivore.feature.save.SaveState
import app.omnivore.omnivore.feature.save.SaveViewModel
import app.omnivore.omnivore.feature.savedItemViews.SavedItemCard
import app.omnivore.omnivore.navigation.Routes
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.library
package app.omnivore.omnivore.feature.library
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -7,27 +7,27 @@ import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.omnivore.omnivore.DatastoreKeys
import app.omnivore.omnivore.DatastoreRepository
import app.omnivore.omnivore.utils.DatastoreKeys
import app.omnivore.omnivore.core.datastore.DatastoreRepository
import app.omnivore.omnivore.R
import app.omnivore.omnivore.dataService.DataService
import app.omnivore.omnivore.dataService.archiveSavedItem
import app.omnivore.omnivore.dataService.deleteSavedItem
import app.omnivore.omnivore.dataService.fetchSavedItemContent
import app.omnivore.omnivore.dataService.isSavedItemContentStoredInDB
import app.omnivore.omnivore.dataService.librarySearch
import app.omnivore.omnivore.dataService.sync
import app.omnivore.omnivore.dataService.syncLabels
import app.omnivore.omnivore.dataService.syncOfflineItemsWithServerIfNeeded
import app.omnivore.omnivore.dataService.unarchiveSavedItem
import app.omnivore.omnivore.dataService.updateWebReadingProgress
import app.omnivore.omnivore.core.data.DataService
import app.omnivore.omnivore.core.data.archiveSavedItem
import app.omnivore.omnivore.core.data.deleteSavedItem
import app.omnivore.omnivore.core.data.fetchSavedItemContent
import app.omnivore.omnivore.core.data.isSavedItemContentStoredInDB
import app.omnivore.omnivore.core.data.librarySearch
import app.omnivore.omnivore.core.data.sync
import app.omnivore.omnivore.core.data.syncLabels
import app.omnivore.omnivore.core.data.syncOfflineItemsWithServerIfNeeded
import app.omnivore.omnivore.core.data.unarchiveSavedItem
import app.omnivore.omnivore.core.data.updateWebReadingProgress
import app.omnivore.omnivore.graphql.generated.type.CreateLabelInput
import app.omnivore.omnivore.network.Networker
import app.omnivore.omnivore.network.createNewLabel
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.ui.ResourceProvider
import app.omnivore.omnivore.ui.setSavedItemLabels
import app.omnivore.omnivore.core.network.Networker
import app.omnivore.omnivore.core.network.createNewLabel
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.feature.ResourceProvider
import app.omnivore.omnivore.feature.setSavedItemLabels
import com.apollographql.apollo3.api.Optional
import com.google.gson.Gson
import dagger.hilt.android.lifecycle.HiltViewModel
@ -198,12 +198,6 @@ class LibraryViewModel @Inject constructor(
}
}
// fun sortKey(appliedSortKey: String) {
// when(appliedSortKey) {
//
// }
// }
private fun handleFilterChanges() {
librarySearchCursor = null
@ -344,6 +338,20 @@ class LibraryViewModel @Inject constructor(
)
}
}
SavedItemAction.MarkUnread -> {
viewModelScope.launch {
dataService.updateWebReadingProgress(
jsonString = Gson().toJson(
mapOf(
"id" to itemID,
"readingProgressPercent" to 0,
"readingProgressAnchorIndex" to 0
)
)
)
}
}
}
actionsMenuItemLiveData.postValue(null)
}
@ -442,5 +450,6 @@ enum class SavedItemAction {
Unarchive,
EditLabels,
EditInfo,
MarkRead
MarkRead,
MarkUnread
}

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.library
package app.omnivore.omnivore.feature.library
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem

View File

@ -0,0 +1,48 @@
package app.omnivore.omnivore.feature.library
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import app.omnivore.omnivore.R
import app.omnivore.omnivore.core.database.entities.SavedItem
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
@Composable
fun SavedItemLibraryContextMenu(
savedItemViewModel: SavedItemViewModel,
savedItem: SavedItem,
isExpanded: Boolean,
onDismiss: () -> Unit,
) {
val menuOptions = listOf(
if (savedItemViewModel.actionsMenuItemLiveData.value?.savedItem?.readingProgress == 100.0) {
MenuItemOption(R.string.saved_item_context_menu_action_mark_unread, SavedItemAction.MarkUnread)
} else {
MenuItemOption(R.string.saved_item_context_menu_action_mark_read, SavedItemAction.MarkRead)
}
)
DropdownMenu(
expanded = isExpanded,
onDismissRequest = onDismiss
) {
menuOptions.forEach { option ->
DropdownMenuItem(
text = { Text( text = stringResource(option.textResourceId), fontWeight = FontWeight.Normal) },
onClick = {
savedItemViewModel.handleSavedItemAction(savedItem.savedItemId, option.action)
onDismiss()
}
)
}
}
}
data class MenuItemOption(
val textResourceId: Int,
val action: SavedItemAction,
val customAction: (() -> Unit)? = null
)

View File

@ -0,0 +1,39 @@
package app.omnivore.omnivore.feature.library
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
enum class SavedItemSortFilter(
val displayText: String,
val rawValue: String,
val queryString: String
) {
NEWEST("Newest", rawValue = "newest", "sort:saved"),
OLDEST("Oldest", rawValue = "oldest", "sort:saved-ASC"),
RECENTLY_READ("Recently Read", rawValue = "recentlyRead", "sort:read"),
RECENTLY_PUBLISHED("Recently Published", rawValue = "recentlyPublished", "sort:published"),
}
@Composable
fun SavedItemSortFilterContextMenu(
isExpanded: Boolean,
onDismiss: () -> Unit,
actionHandler: (SavedItemSortFilter) -> Unit
) {
DropdownMenu(
expanded = isExpanded,
onDismissRequest = onDismiss
) {
SavedItemSortFilter.values().forEach {
DropdownMenuItem(
text = { Text(it.displayText) },
onClick = {
actionHandler(it)
onDismiss()
}
)
}
}
}

View File

@ -1,7 +1,7 @@
package app.omnivore.omnivore.ui.library
package app.omnivore.omnivore.feature.library
import androidx.lifecycle.MutableLiveData
import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
interface SavedItemViewModel {

View File

@ -0,0 +1,226 @@
package app.omnivore.omnivore.feature.library
import android.content.Intent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
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.core.database.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.core.database.entities.TypeaheadCardData
import app.omnivore.omnivore.feature.reader.PDFReaderActivity
import app.omnivore.omnivore.feature.reader.WebReaderLoadingContainerActivity
import app.omnivore.omnivore.feature.savedItemViews.SavedItemCard
import app.omnivore.omnivore.feature.savedItemViews.TypeaheadSearchCard
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchView(
viewModel: SearchViewModel,
navController: NavHostController
) {
val isRefreshing: Boolean by viewModel.isRefreshing.observeAsState(false)
val typeaheadMode: Boolean by viewModel.typeaheadMode.observeAsState(true)
val searchText: String by viewModel.searchTextLiveData.observeAsState("")
val actionsMenuItem: SavedItemWithLabelsAndHighlights? by viewModel.actionsMenuItemLiveData.observeAsState(
null
)
Scaffold(
topBar = {
TopAppBar(
title = { Text("") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant
),
navigationIcon = {
if (actionsMenuItem != null) {
IconButton(onClick = {
viewModel.actionsMenuItemLiveData.postValue(null)
}) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
modifier = Modifier,
contentDescription = "Back"
)
}
}
},
actions = {
if (actionsMenuItem != null) {
IconButton(onClick = { }) {
Icon(
painter = painterResource(id = R.drawable.archive_outline),
contentDescription = null
)
}
IconButton(onClick = { }) {
Icon(
painter = painterResource(id = R.drawable.tag),
contentDescription = null
)
}
IconButton(onClick = { }) {
Icon(
imageVector = Icons.Outlined.Delete,
contentDescription = null
)
}
IconButton(onClick = { }) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = null
)
}
} else {
Row {
SearchField(
searchText,
onSearch = {
viewModel.performSearch()
},
onSearchTextChanged = { viewModel.updateSearchText(it) },
navController = navController
)
}
}
}
)
}
) { paddingValues ->
if (isRefreshing) {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.padding(top = paddingValues.calculateTopPadding())
.fillMaxWidth()
) {
CircularProgressIndicator(
modifier = Modifier
.height(45.dp)
.width(45.dp),
strokeWidth = 5.dp,
color = colorResource(R.color.green_55B938)
)
}
} else if (typeaheadMode) {
TypeaheadSearchViewContent(
viewModel,
modifier = Modifier
.padding(top = paddingValues.calculateTopPadding())
)
} else {
SearchViewContent(
viewModel,
modifier = Modifier
.padding(top = paddingValues.calculateTopPadding())
)
}
}
}
@Composable
fun TypeaheadSearchViewContent(viewModel: SearchViewModel, modifier: Modifier) {
val context = LocalContext.current
val listState = rememberLazyListState()
val searchedCardsData: List<TypeaheadCardData> by viewModel.searchItemsLiveData.observeAsState(
listOf()
)
LazyColumn(
state = listState,
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
) {
items(searchedCardsData) { cardData ->
TypeaheadSearchCard(
cardData = cardData,
onClickHandler = {
// val activityClass = if (cardData.isPDF()) PDFReaderActivity::class.java else WebReaderLoadingContainerActivity::class.java
val activityClass = WebReaderLoadingContainerActivity::class.java
val intent = Intent(context, activityClass)
intent.putExtra("SAVED_ITEM_SLUG", cardData.slug)
context.startActivity(intent)
},
actionHandler = { viewModel.handleSavedItemAction(cardData.savedItemId, it) }
)
}
}
}
@Composable
fun SearchViewContent(viewModel: SearchViewModel, modifier: Modifier) {
val context = LocalContext.current
val listState = rememberLazyListState()
val cardsData: List<SavedItemWithLabelsAndHighlights> by viewModel.itemsLiveData.observeAsState(
listOf()
)
LazyColumn(
state = listState,
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
) {
items(cardsData) { cardDataWithLabels ->
SavedItemCard(
selected = false,
savedItemViewModel = viewModel,
savedItem = cardDataWithLabels,
onClickHandler = {
val activityClass =
if (cardDataWithLabels.savedItem.contentReader == "PDF") PDFReaderActivity::class.java else WebReaderLoadingContainerActivity::class.java
val intent = Intent(context, activityClass)
intent.putExtra("SAVED_ITEM_SLUG", cardDataWithLabels.savedItem.slug)
context.startActivity(intent)
},
actionHandler = {
viewModel.handleSavedItemAction(
cardDataWithLabels.savedItem.savedItemId,
it
)
}
)
}
}
}

View File

@ -1,16 +1,27 @@
package app.omnivore.omnivore.ui.library
package app.omnivore.omnivore.feature.library
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.omnivore.omnivore.*
import app.omnivore.omnivore.dataService.*
import app.omnivore.omnivore.network.*
import app.omnivore.omnivore.persistence.entities.*
import app.omnivore.omnivore.core.data.DataService
import app.omnivore.omnivore.core.data.archiveSavedItem
import app.omnivore.omnivore.core.data.deleteSavedItem
import app.omnivore.omnivore.core.data.isSavedItemContentStoredInDB
import app.omnivore.omnivore.core.data.librarySearch
import app.omnivore.omnivore.core.data.unarchiveSavedItem
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.core.database.entities.TypeaheadCardData
import app.omnivore.omnivore.core.datastore.DatastoreRepository
import app.omnivore.omnivore.core.network.Networker
import app.omnivore.omnivore.core.network.typeaheadSearch
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@HiltViewModel
@ -18,7 +29,7 @@ class SearchViewModel @Inject constructor(
private val networker: Networker,
private val dataService: DataService,
private val datastoreRepo: DatastoreRepository
): ViewModel(), SavedItemViewModel {
) : ViewModel(), SavedItemViewModel {
private val contentRequestChannel = Channel<String>(capacity = Channel.UNLIMITED)
private var cursor: String? = null
@ -66,7 +77,10 @@ class SearchViewModel @Inject constructor(
private fun loadUsingSearchAPI() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val result = dataService.librarySearch(cursor = librarySearchCursor, query = searchQueryString())
val result = dataService.librarySearch(
cursor = librarySearchCursor,
query = searchQueryString()
)
result.cursor?.let {
librarySearchCursor = it
}
@ -81,29 +95,29 @@ class SearchViewModel @Inject constructor(
}
val newItems = result.savedItems
/*
.map {
SavedItemCardDataWithLabels(
cardData = SavedItemCardData(
savedItemId = it.savedItem.savedItemId,
slug = it.savedItem.slug,
publisherURLString = it.savedItem.publisherURLString,
title = it.savedItem.title,
author = it.savedItem.author,
imageURLString = it.savedItem.imageURLString,
isArchived = it.savedItem.isArchived,
pageURLString = it.savedItem.pageURLString,
contentReader = it.savedItem.contentReader,
savedAt = it.savedItem.savedAt,
readingProgress = it.savedItem.readingProgress,
wordsCount = it.savedItem.wordsCount
),
labels = listOf()
)
}
*/
/*
.map {
SavedItemCardDataWithLabels(
cardData = SavedItemCardData(
savedItemId = it.savedItem.savedItemId,
slug = it.savedItem.slug,
publisherURLString = it.savedItem.publisherURLString,
title = it.savedItem.title,
author = it.savedItem.author,
imageURLString = it.savedItem.imageURLString,
isArchived = it.savedItem.isArchived,
pageURLString = it.savedItem.pageURLString,
contentReader = it.savedItem.contentReader,
savedAt = it.savedItem.savedAt,
readingProgress = it.savedItem.readingProgress,
wordsCount = it.savedItem.wordsCount
),
labels = listOf()
)
}
*/
itemsLiveData.value?.let{
itemsLiveData.value?.let {
itemsLiveData.postValue(newItems + it)
} ?: run {
itemsLiveData.postValue(newItems)
@ -146,24 +160,29 @@ class SearchViewModel @Inject constructor(
dataService.deleteSavedItem(itemID)
}
}
SavedItemAction.Archive -> {
viewModelScope.launch {
dataService.archiveSavedItem(itemID)
}
}
SavedItemAction.Unarchive -> {
viewModelScope.launch {
dataService.unarchiveSavedItem(itemID)
}
}
SavedItemAction.EditLabels -> {
// TODO
}
SavedItemAction.EditInfo -> {
// TODO
}
SavedItemAction.MarkRead -> TODO()
SavedItemAction.MarkUnread -> TODO()
}
actionsMenuItemLiveData.postValue(null)
}

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.notebook
package app.omnivore.omnivore.feature.notebook
import android.content.ClipData
import android.content.ClipboardManager
@ -6,7 +6,6 @@ import android.content.Context
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
@ -36,11 +35,11 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import app.omnivore.omnivore.R
import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
import dev.jeziellago.compose.markdowntext.MarkdownText
import kotlinx.coroutines.launch
import app.omnivore.omnivore.persistence.entities.Highlight
import app.omnivore.omnivore.ui.theme.OmnivoreTheme
import app.omnivore.omnivore.core.database.entities.Highlight
import app.omnivore.omnivore.feature.theme.OmnivoreTheme
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.res.stringResource

View File

@ -1,13 +1,13 @@
package app.omnivore.omnivore.ui.notebook
package app.omnivore.omnivore.feature.notebook
import androidx.lifecycle.*
import app.omnivore.omnivore.dataService.DataService
import app.omnivore.omnivore.dataService.createNoteHighlight
import app.omnivore.omnivore.core.data.DataService
import app.omnivore.omnivore.core.data.createNoteHighlight
import app.omnivore.omnivore.graphql.generated.type.UpdateHighlightInput
import app.omnivore.omnivore.network.Networker
import app.omnivore.omnivore.network.updateHighlight
import app.omnivore.omnivore.persistence.entities.Highlight
import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.core.network.Networker
import app.omnivore.omnivore.core.network.updateHighlight
import app.omnivore.omnivore.core.database.entities.Highlight
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
import com.apollographql.apollo3.api.Optional
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.reader
package app.omnivore.omnivore.feature.reader
import android.content.DialogInterface
import android.os.Bundle
@ -9,8 +9,8 @@ import android.view.WindowManager
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.DialogFragment
import app.omnivore.omnivore.ui.notebook.EditNoteModal
import app.omnivore.omnivore.ui.theme.OmnivoreTheme
import app.omnivore.omnivore.feature.notebook.EditNoteModal
import app.omnivore.omnivore.feature.theme.OmnivoreTheme
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import com.google.android.material.bottomsheet.BottomSheetDialog

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.reader
package app.omnivore.omnivore.feature.reader
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.reader
package app.omnivore.omnivore.feature.reader
import android.annotation.SuppressLint
import android.content.ClipData
@ -19,7 +19,7 @@ import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.lifecycle.Observer
import app.omnivore.omnivore.R
import app.omnivore.omnivore.persistence.entities.Highlight
import app.omnivore.omnivore.core.database.entities.Highlight
import com.pspdfkit.annotations.Annotation
import com.pspdfkit.annotations.HighlightAnnotation
import com.pspdfkit.configuration.PdfConfiguration

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.reader
package app.omnivore.omnivore.feature.reader
import android.content.Context
import android.net.Uri
@ -6,13 +6,20 @@ import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.omnivore.omnivore.dataService.DataService
import app.omnivore.omnivore.dataService.NanoId
import app.omnivore.omnivore.core.network.Networker
import app.omnivore.omnivore.core.network.ReadingProgressParams
import app.omnivore.omnivore.core.network.createHighlight
import app.omnivore.omnivore.core.network.deleteHighlights
import app.omnivore.omnivore.core.network.mergeHighlights
import app.omnivore.omnivore.core.network.savedItem
import app.omnivore.omnivore.core.network.updateHighlight
import app.omnivore.omnivore.core.network.updateReadingProgress
import app.omnivore.omnivore.core.data.DataService
import app.omnivore.omnivore.core.data.NanoId
import app.omnivore.omnivore.graphql.generated.type.CreateHighlightInput
import app.omnivore.omnivore.graphql.generated.type.MergeHighlightInput
import app.omnivore.omnivore.graphql.generated.type.UpdateHighlightInput
import app.omnivore.omnivore.persistence.entities.SavedItem
import app.omnivore.omnivore.network.*
import app.omnivore.omnivore.core.database.entities.SavedItem
import com.apollographql.apollo3.api.Optional
import com.google.gson.Gson
import com.pspdfkit.annotations.Annotation

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.reader
package app.omnivore.omnivore.feature.reader
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.isSystemInDarkTheme
@ -24,7 +24,7 @@ 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.theme.OmnivoreTheme
import app.omnivore.omnivore.feature.theme.OmnivoreTheme
@Composable
fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.reader
package app.omnivore.omnivore.feature.reader
import android.annotation.SuppressLint
import android.content.ClipData

View File

@ -1,8 +1,8 @@
package app.omnivore.omnivore.ui.reader
package app.omnivore.omnivore.feature.reader
import android.util.Log
import app.omnivore.omnivore.persistence.entities.SavedItem
import app.omnivore.omnivore.persistence.entities.Highlight
import app.omnivore.omnivore.core.database.entities.SavedItem
import app.omnivore.omnivore.core.database.entities.Highlight
import com.google.gson.Gson
enum class WebFont(val displayText: String, val rawValue: String) {

View File

@ -0,0 +1,528 @@
package app.omnivore.omnivore.feature.reader
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.activity.ComponentActivity
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
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.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import app.omnivore.omnivore.MainActivity
import app.omnivore.omnivore.R
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.feature.components.LabelsSelectionSheetContent2
import app.omnivore.omnivore.feature.components.LabelsViewModel
import app.omnivore.omnivore.feature.editinfo.EditInfoSheetContent
import app.omnivore.omnivore.feature.editinfo.EditInfoViewModel
import app.omnivore.omnivore.feature.notebook.EditNoteModal
import app.omnivore.omnivore.feature.notebook.NotebookView
import app.omnivore.omnivore.feature.notebook.NotebookViewModel
import app.omnivore.omnivore.feature.savedItemViews.SavedItemContextMenu
import app.omnivore.omnivore.feature.theme.OmnivoreTheme
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@AndroidEntryPoint
class WebReaderLoadingContainerActivity : ComponentActivity() {
val viewModel: WebReaderViewModel by viewModels()
private val notebookViewModel: NotebookViewModel by viewModels()
private val labelsViewModel: LabelsViewModel by viewModels()
private val editInfoViewModel: EditInfoViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val requestID = intent.getStringExtra("SAVED_ITEM_REQUEST_ID")
val slug = intent.getStringExtra("SAVED_ITEM_SLUG")
viewModel.loadItem(slug = slug, requestID = requestID)
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)
) {
if (viewModel.hasFetchError.value == true) {
Text(stringResource(R.string.web_reader_loading_container_error_msg))
} else {
WebReaderLoadingContainer(
onLibraryIconTap = if (requestID != null) {
{ startMainActivity() }
} else null,
webReaderViewModel = viewModel,
notebookViewModel = notebookViewModel,
labelsViewModel = labelsViewModel,
editInfoViewModel = editInfoViewModel,
)
}
}
}
}
// animate the view up when keyboard appears
WindowCompat.setDecorFitsSystemWindows(window, false)
val rootView = findViewById<View>(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)
}
}
enum class BottomSheetState {
NONE,
PREFERENCES,
NOTEBOOK,
EDITNOTE,
HIGHLIGHTNOTE,
LABELS,
LINK,
EDIT_INFO,
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun WebReaderLoadingContainer(
onLibraryIconTap: (() -> Unit)? = null,
webReaderViewModel: WebReaderViewModel,
notebookViewModel: NotebookViewModel,
labelsViewModel: LabelsViewModel,
editInfoViewModel: EditInfoViewModel
) {
val currentThemeKey = webReaderViewModel.currentThemeKey.observeAsState()
val currentTheme = Themes.values().find { it.themeKey == currentThemeKey.value }
val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
val bottomSheetState: BottomSheetState? by webReaderViewModel.bottomSheetStateLiveData.observeAsState(
BottomSheetState.NONE
)
val webReaderParams: WebReaderParams? by webReaderViewModel.webReaderParamsLiveData.observeAsState(
null
)
val shouldPopView: Boolean by webReaderViewModel.shouldPopViewLiveData.observeAsState(false)
val labels: List<SavedItemLabel> by webReaderViewModel.savedItemLabelsLiveData.observeAsState(
listOf()
)
val maxToolbarHeight = 48.dp
webReaderViewModel.maxToolbarHeightPx =
with(LocalDensity.current) { maxToolbarHeight.roundToPx().toFloat() }
val coroutineScope = rememberCoroutineScope()
val styledContent = webReaderParams?.let {
val webReaderContent = WebReaderContent(
preferences = webReaderViewModel.storedWebPreferences(isSystemInDarkTheme()),
item = it.item,
articleContent = it.articleContent,
)
webReaderContent.styledContent()
}
val modalBottomSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
skipHalfExpanded = bottomSheetState == BottomSheetState.EDITNOTE || bottomSheetState == BottomSheetState.HIGHLIGHTNOTE,
confirmValueChange = {
if (it == ModalBottomSheetValue.Hidden) {
webReaderViewModel.resetBottomSheet()
}
true
}
)
val showMenu = {
coroutineScope.launch {
modalBottomSheetState.show()
}
}
when (bottomSheetState) {
BottomSheetState.PREFERENCES -> {
coroutineScope.launch {
if (!modalBottomSheetState.isVisible) {
modalBottomSheetState.show()
}
}
}
BottomSheetState.NOTEBOOK, BottomSheetState.EDITNOTE,
BottomSheetState.HIGHLIGHTNOTE, BottomSheetState.LABELS, BottomSheetState.EDIT_INFO,
BottomSheetState.LINK,
-> {
showMenu()
}
else -> {
coroutineScope.launch {
modalBottomSheetState.hide()
}
}
}
ModalBottomSheetLayout(
modifier = Modifier
.statusBarsPadding(),
sheetBackgroundColor = Color.Transparent,
sheetState = modalBottomSheetState,
sheetContent = {
when (bottomSheetState) {
BottomSheetState.PREFERENCES -> {
BottomSheetUI {
ReaderPreferencesView(webReaderViewModel)
}
}
BottomSheetState.NOTEBOOK -> {
webReaderParams?.let { params ->
BottomSheetUI {
NotebookView(
savedItemId = params.item.savedItemId,
viewModel = notebookViewModel,
onEditNote = {
notebookViewModel.highlightUnderEdit = it
webReaderViewModel.setBottomSheet(BottomSheetState.EDITNOTE)
})
}
}
}
BottomSheetState.EDITNOTE -> {
webReaderParams?.let { params ->
EditNoteModal(
initialValue = notebookViewModel.highlightUnderEdit?.annotation,
onDismiss = { save, note ->
coroutineScope.launch {
if (save) {
notebookViewModel.highlightUnderEdit?.let { highlight ->
notebookViewModel.updateHighlightNote(
highlight.highlightId,
note
)
} ?: run {
if (note != null) {
notebookViewModel.addArticleNote(
savedItemId = params.item.savedItemId,
note = note
)
}
}
}
notebookViewModel.highlightUnderEdit = null
}
webReaderViewModel.setBottomSheet(BottomSheetState.NOTEBOOK)
})
}
}
BottomSheetState.HIGHLIGHTNOTE -> {
EditNoteModal(
initialValue = webReaderViewModel.annotation,
onDismiss = { save, note ->
coroutineScope.launch {
if (save) {
webReaderViewModel.saveAnnotation(note ?: "")
} else {
webReaderViewModel.cancelAnnotation()
}
webReaderViewModel.annotation = null
}
webReaderViewModel.resetBottomSheet()
}
)
}
BottomSheetState.LABELS -> {
BottomSheetUI {
LabelsSelectionSheetContent2(
labels = labels,
labelsViewModel = labelsViewModel,
initialSelectedLabels = webReaderParams?.labels ?: listOf(),
onCancel = {
coroutineScope.launch {
webReaderViewModel.resetBottomSheet()
}
},
isLibraryMode = false,
onSave = {
if (it != labels) {
webReaderViewModel.updateSavedItemLabels(
savedItemID = webReaderParams?.item?.savedItemId ?: "",
labels = it
)
}
coroutineScope.launch {
webReaderViewModel.resetBottomSheet()
}
},
onCreateLabel = { newLabelName, labelHexValue ->
webReaderViewModel.createNewSavedItemLabel(
newLabelName,
labelHexValue
)
}
)
}
}
BottomSheetState.EDIT_INFO -> {
BottomSheetUI {
EditInfoSheetContent(
savedItemId = webReaderParams?.item?.savedItemId,
title = webReaderParams?.item?.title,
author = webReaderParams?.item?.author,
description = webReaderParams?.item?.descriptionText,
viewModel = editInfoViewModel,
onCancel = {
coroutineScope.launch {
webReaderViewModel.resetBottomSheet()
}
},
onUpdated = {
coroutineScope.launch {
webReaderViewModel.updateItemTitle()
webReaderViewModel.resetBottomSheet()
}
}
)
}
}
BottomSheetState.LINK -> {
BottomSheetUI {
OpenLinkView(webReaderViewModel)
}
}
BottomSheetState.NONE -> {
}
else -> {
}
}
Spacer(modifier = Modifier.weight(1.0F))
}
) {
Scaffold(
topBar = {
ReaderTopAppBar(webReaderViewModel, onLibraryIconTap)
}) { paddingValues ->
if (styledContent != null) {
WebReader(
styledContent = styledContent,
webReaderViewModel = webReaderViewModel,
currentTheme = currentTheme,
)
}
LaunchedEffect(shouldPopView) {
if (shouldPopView) {
onBackPressedDispatcher?.onBackPressed()
}
}
}
}
}
@Composable
fun ReaderTopAppBar(
webReaderViewModel: WebReaderViewModel,
onLibraryIconTap: (() -> Unit)? = null
) {
val context = LocalContext.current
val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
val isDarkMode = isSystemInDarkTheme()
val currentThemeKey = webReaderViewModel.currentThemeKey.observeAsState()
val currentTheme = Themes.values().find { it.themeKey == currentThemeKey.value }
val toolbarHeightPx: Float by webReaderViewModel.currentToolbarHeightLiveData.observeAsState(
0.0f
)
val webReaderParams: WebReaderParams? by webReaderViewModel.webReaderParamsLiveData.observeAsState(
null
)
var isMenuExpanded by remember { mutableStateOf(false) }
val themeBackgroundColor = currentTheme?.let {
if (it.themeKey == "System" && isDarkMode) {
Color(0xFF000000)
} else if (it.themeKey == "System") {
Color(0xFFFFFFFF)
} else {
Color(it.backgroundColor)
}
} ?: Color(0xFFFFFFFF)
val themeTintColor = currentTheme?.let {
if (it.themeKey == "System" && isDarkMode) {
Color(0xFFFFFFFF)
} else if (it.themeKey == "System") {
Color(0xFF000000)
} else {
Color(it.foregroundColor)
}
} ?: Color(0xFF000000)
TopAppBar(
modifier = Modifier
.height(height = with(LocalDensity.current) {
toolbarHeightPx.roundToInt().toDp()
}),
backgroundColor = themeBackgroundColor,
elevation = 0.dp,
title = {},
navigationIcon = {
IconButton(onClick = {
onBackPressedDispatcher?.onBackPressed()
}) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
modifier = Modifier,
contentDescription = "Back",
tint = themeTintColor
)
}
},
actions = {
if (onLibraryIconTap != null) {
IconButton(onClick = { onLibraryIconTap() }) {
Icon(
imageVector = Icons.Default.Home,
contentDescription = null,
tint = themeTintColor,
)
}
}
webReaderParams?.let {
IconButton(onClick = {
webReaderViewModel.setBottomSheet(BottomSheetState.NOTEBOOK)
}) {
Icon(
painter = painterResource(id = R.drawable.notebook),
contentDescription = null,
tint = themeTintColor
)
}
}
IconButton(onClick = {
webReaderViewModel.setBottomSheet(BottomSheetState.PREFERENCES)
}) {
Icon(
painter = painterResource(id = R.drawable.format_letter_case),
contentDescription = 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(
context = context,
isExpanded = true,
isArchived = params.item.isArchived,
onDismiss = { isMenuExpanded = false },
webReaderViewModel = webReaderViewModel,
actionHandler = {
webReaderViewModel.handleSavedItemAction(
params.item.savedItemId,
it
)
}
)
}
}
}
},
)
}
@Composable
fun BottomSheetUI(content: @Composable () -> Unit) {
Box(
modifier = Modifier
.wrapContentHeight()
.fillMaxWidth()
.clip(RoundedCornerShape(topEnd = 20.dp, topStart = 20.dp))
.background(Color.White)
.statusBarsPadding()
) {
Scaffold { paddingValues ->
Box(modifier = Modifier.fillMaxSize()) {
content()
}
}
}
}

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.reader
package app.omnivore.omnivore.feature.reader
import android.content.ClipData
import android.content.ClipboardManager
@ -13,29 +13,29 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import app.omnivore.omnivore.DatastoreKeys
import app.omnivore.omnivore.DatastoreRepository
import app.omnivore.omnivore.EventTracker
import app.omnivore.omnivore.utils.DatastoreKeys
import app.omnivore.omnivore.core.datastore.DatastoreRepository
import app.omnivore.omnivore.core.analytics.EventTracker
import app.omnivore.omnivore.R
import app.omnivore.omnivore.dataService.DataService
import app.omnivore.omnivore.dataService.archiveSavedItem
import app.omnivore.omnivore.dataService.createWebHighlight
import app.omnivore.omnivore.dataService.deleteHighlightFromJSON
import app.omnivore.omnivore.dataService.deleteSavedItem
import app.omnivore.omnivore.dataService.mergeWebHighlights
import app.omnivore.omnivore.dataService.unarchiveSavedItem
import app.omnivore.omnivore.dataService.updateWebHighlight
import app.omnivore.omnivore.dataService.updateWebReadingProgress
import app.omnivore.omnivore.core.data.DataService
import app.omnivore.omnivore.core.data.archiveSavedItem
import app.omnivore.omnivore.core.data.createWebHighlight
import app.omnivore.omnivore.core.data.deleteHighlightFromJSON
import app.omnivore.omnivore.core.data.deleteSavedItem
import app.omnivore.omnivore.core.data.mergeWebHighlights
import app.omnivore.omnivore.core.data.unarchiveSavedItem
import app.omnivore.omnivore.core.data.updateWebHighlight
import app.omnivore.omnivore.core.data.updateWebReadingProgress
import app.omnivore.omnivore.graphql.generated.type.CreateLabelInput
import app.omnivore.omnivore.network.Networker
import app.omnivore.omnivore.network.createNewLabel
import app.omnivore.omnivore.network.saveUrl
import app.omnivore.omnivore.network.savedItem
import app.omnivore.omnivore.persistence.entities.SavedItem
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.ui.components.HighlightColor
import app.omnivore.omnivore.ui.library.SavedItemAction
import app.omnivore.omnivore.ui.setSavedItemLabels
import app.omnivore.omnivore.core.network.Networker
import app.omnivore.omnivore.core.network.createNewLabel
import app.omnivore.omnivore.core.network.saveUrl
import app.omnivore.omnivore.core.network.savedItem
import app.omnivore.omnivore.core.database.entities.SavedItem
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.feature.components.HighlightColor
import app.omnivore.omnivore.feature.library.SavedItemAction
import app.omnivore.omnivore.feature.setSavedItemLabels
import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull
import com.google.gson.Gson
import dagger.hilt.android.lifecycle.HiltViewModel
@ -345,6 +345,20 @@ class WebReaderViewModel @Inject constructor(
)
}
}
SavedItemAction.MarkUnread -> {
viewModelScope.launch {
dataService.updateWebReadingProgress(
jsonString = Gson().toJson(
mapOf(
"id" to itemID,
"readingProgressPercent" to 0,
"readingProgressAnchorIndex" to 0
)
)
)
}
}
}
}

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.root
package app.omnivore.omnivore.feature.root
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
@ -8,19 +8,19 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import app.omnivore.omnivore.Routes
import app.omnivore.omnivore.ui.auth.LoginViewModel
import app.omnivore.omnivore.ui.auth.WelcomeScreen
import app.omnivore.omnivore.ui.components.LabelsViewModel
import app.omnivore.omnivore.ui.editinfo.EditInfoViewModel
import app.omnivore.omnivore.ui.library.LibraryView
import app.omnivore.omnivore.ui.library.LibraryViewModel
import app.omnivore.omnivore.ui.library.SearchView
import app.omnivore.omnivore.ui.library.SearchViewModel
import app.omnivore.omnivore.ui.save.SaveViewModel
import app.omnivore.omnivore.ui.settings.PolicyWebView
import app.omnivore.omnivore.ui.settings.SettingsView
import app.omnivore.omnivore.ui.settings.SettingsViewModel
import app.omnivore.omnivore.navigation.Routes
import app.omnivore.omnivore.feature.auth.LoginViewModel
import app.omnivore.omnivore.feature.auth.WelcomeScreen
import app.omnivore.omnivore.feature.components.LabelsViewModel
import app.omnivore.omnivore.feature.editinfo.EditInfoViewModel
import app.omnivore.omnivore.feature.library.LibraryView
import app.omnivore.omnivore.feature.library.LibraryViewModel
import app.omnivore.omnivore.feature.library.SearchView
import app.omnivore.omnivore.feature.library.SearchViewModel
import app.omnivore.omnivore.feature.save.SaveViewModel
import app.omnivore.omnivore.feature.settings.PolicyWebView
import app.omnivore.omnivore.feature.settings.SettingsView
import app.omnivore.omnivore.feature.settings.SettingsViewModel
@Composable
fun RootView(

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.save
package app.omnivore.omnivore.feature.save
import android.content.Intent
import androidx.compose.foundation.background
@ -16,7 +16,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import app.omnivore.omnivore.R
import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainerActivity
import app.omnivore.omnivore.feature.reader.WebReaderLoadingContainerActivity
import kotlinx.coroutines.launch
@Composable

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.save
package app.omnivore.omnivore.feature.save
import android.content.ContentValues
import android.content.Intent
@ -12,7 +12,6 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment.Companion.TopCenter

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.save
package app.omnivore.omnivore.feature.save
import android.util.Patterns
import androidx.compose.runtime.getValue
@ -8,13 +8,13 @@ import androidx.compose.ui.text.intl.Locale
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.omnivore.omnivore.Constants
import app.omnivore.omnivore.DatastoreKeys
import app.omnivore.omnivore.DatastoreRepository
import app.omnivore.omnivore.utils.Constants
import app.omnivore.omnivore.utils.DatastoreKeys
import app.omnivore.omnivore.core.datastore.DatastoreRepository
import app.omnivore.omnivore.R
import app.omnivore.omnivore.graphql.generated.SaveUrlMutation
import app.omnivore.omnivore.graphql.generated.type.SaveUrlInput
import app.omnivore.omnivore.ui.ResourceProvider
import app.omnivore.omnivore.feature.ResourceProvider
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.Optional
import dagger.hilt.android.lifecycle.HiltViewModel

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.savedItemViews
package app.omnivore.omnivore.feature.savedItemViews
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
@ -19,12 +19,12 @@ import androidx.compose.ui.text.toLowerCase
import androidx.compose.ui.text.toUpperCase
import androidx.compose.ui.unit.*
import app.omnivore.omnivore.R
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.ui.components.LabelChip
import app.omnivore.omnivore.ui.components.LabelChipColors
import app.omnivore.omnivore.ui.library.SavedItemAction
import app.omnivore.omnivore.ui.library.SavedItemViewModel
import app.omnivore.omnivore.core.database.entities.SavedItemLabel
import app.omnivore.omnivore.core.database.entities.SavedItemWithLabelsAndHighlights
import app.omnivore.omnivore.feature.components.LabelChipColors
import app.omnivore.omnivore.feature.components.LabelFilterChip
import app.omnivore.omnivore.feature.library.SavedItemAction
import app.omnivore.omnivore.feature.library.SavedItemViewModel
import coil.compose.rememberAsyncImagePainter
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
@ -108,9 +108,10 @@ fun SavedItemCard(
savedItem.labels.filter { !isFlairLabel(it) }.sortedWith(compareBy { it.name.toLowerCase(Locale.current) }).forEach { label ->
val chipColors = LabelChipColors.fromHex(label.color)
LabelChip(
name = label.name,
colors = chipColors,
LabelFilterChip(
onClick = { },
label = label.name,
//colors = chipColors,
)
}
}

View File

@ -0,0 +1,72 @@
package app.omnivore.omnivore.feature.savedItemViews
import android.content.Context
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import app.omnivore.omnivore.R
import app.omnivore.omnivore.feature.library.SavedItemAction
import app.omnivore.omnivore.feature.reader.WebReaderViewModel
@Composable
fun SavedItemContextMenu(
isExpanded: Boolean,
isArchived: Boolean,
context: Context,
webReaderViewModel: WebReaderViewModel,
onDismiss: () -> Unit,
actionHandler: (SavedItemAction) -> Unit
) {
val menuOptions = listOf(
MenuItemOption(R.string.saved_item_context_menu_action_edit_info, SavedItemAction.EditInfo),
MenuItemOption(
R.string.saved_item_context_menu_action_edit_labels,
SavedItemAction.EditLabels
),
MenuItemOption(
textResourceId = if (isArchived) R.string.saved_item_context_menu_action_unarchive else R.string.saved_item_context_menu_action_archive,
action = if (isArchived) SavedItemAction.Unarchive else SavedItemAction.Archive
),
MenuItemOption(
textResourceId = R.string.saved_item_context_menu_action_share_original,
customAction = {
webReaderViewModel.showShareLinkSheet(context)
onDismiss()
}
),
MenuItemOption(R.string.saved_item_context_menu_action_remove_item, SavedItemAction.Delete)
)
DropdownMenu(
expanded = isExpanded,
onDismissRequest = onDismiss
) {
menuOptions.forEach { option ->
if (option.customAction != null) {
DropdownMenuItem(
text = { Text( text = stringResource(option.textResourceId), fontWeight = FontWeight.Normal) },
onClick = {
option.customAction.invoke()
}
)
} else {
DropdownMenuItem(
text = { Text( text = stringResource(option.textResourceId), fontWeight = FontWeight.Normal) },
onClick = {
option.action?.let { actionHandler(it) }
onDismiss()
}
)
}
}
}
}
data class MenuItemOption(
val textResourceId: Int,
val action: SavedItemAction? = null,
val customAction: (() -> Unit)? = null
)

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.savedItemViews
package app.omnivore.omnivore.feature.savedItemViews
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
@ -10,8 +10,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.*
import app.omnivore.omnivore.persistence.entities.TypeaheadCardData
import app.omnivore.omnivore.ui.library.SavedItemAction
import app.omnivore.omnivore.core.database.entities.TypeaheadCardData
import app.omnivore.omnivore.feature.library.SavedItemAction
@OptIn(ExperimentalFoundationApi::class)
@Composable

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.settings
package app.omnivore.omnivore.feature.settings
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.settings
package app.omnivore.omnivore.feature.settings
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.settings
package app.omnivore.omnivore.feature.settings
import android.annotation.SuppressLint
import android.view.ViewGroup
@ -14,7 +14,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController
import app.omnivore.omnivore.Routes
import app.omnivore.omnivore.navigation.Routes
import app.omnivore.omnivore.R
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "SetJavaScriptEnabled")

View File

@ -1,4 +1,4 @@
package app.omnivore.omnivore.ui.settings
package app.omnivore.omnivore.feature.settings
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@ -18,8 +18,8 @@ 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
import app.omnivore.omnivore.navigation.Routes
import app.omnivore.omnivore.feature.auth.LoginViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable

Some files were not shown because too many files have changed in this diff Show More