First pass at Android Notebooks, most parts in place, lots of styling needed
This commit is contained in:
@ -148,6 +148,8 @@ dependencies {
|
||||
implementation "androidx.room:room-runtime:$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'
|
||||
}
|
||||
|
||||
apollo {
|
||||
|
||||
@ -57,5 +57,10 @@
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.Omnivore"/>
|
||||
|
||||
<activity
|
||||
android:name=".ui.notebook.NotebookActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.Omnivore"/>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
Binary file not shown.
@ -7,4 +7,5 @@ sealed class Routes(val route: String) {
|
||||
object Documentation: Routes("Documentation")
|
||||
object PrivacyPolicy: Routes("PrivacyPolicy")
|
||||
object TermsAndConditions: Routes("TermsAndConditions")
|
||||
object Notebook: Routes("Notebook")
|
||||
}
|
||||
|
||||
@ -162,6 +162,22 @@ interface SavedItemDao {
|
||||
)
|
||||
fun getLibraryLiveDataSortedByRecentlyPublished(archiveFilter: Int): LiveData<List<SavedItemCardDataWithLabels>>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"SELECT ${SavedItemQueryConstants.libraryColumns} " +
|
||||
"FROM SavedItem " +
|
||||
"LEFT OUTER JOIN SavedItemAndSavedItemLabelCrossRef on SavedItem.savedItemId = SavedItemAndSavedItemLabelCrossRef.savedItemId " +
|
||||
"LEFT OUTER JOIN SavedItemAndHighlightCrossRef on SavedItem.savedItemId = SavedItemAndHighlightCrossRef.savedItemId " +
|
||||
|
||||
"LEFT OUTER JOIN SavedItemLabel on SavedItemLabel.savedItemLabelId = SavedItemAndSavedItemLabelCrossRef.savedItemLabelId " +
|
||||
"LEFT OUTER JOIN Highlight on highlight.highlightId = SavedItemAndHighlightCrossRef.highlightId " +
|
||||
|
||||
"WHERE SavedItem.savedItemId != :savedItemId " +
|
||||
|
||||
"GROUP BY SavedItem.savedItemId "
|
||||
)
|
||||
fun getLibraryItemById(savedItemId: String): LiveData<SavedItemWithLabelsAndHighlights>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"SELECT ${SavedItemQueryConstants.libraryColumns} " +
|
||||
|
||||
@ -0,0 +1,327 @@
|
||||
package app.omnivore.omnivore.ui.notebook
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.ContextMenu
|
||||
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.border
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.LocalContentColor
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import app.omnivore.omnivore.MainActivity
|
||||
import app.omnivore.omnivore.R
|
||||
import app.omnivore.omnivore.ui.components.WebReaderLabelsSelectionSheet
|
||||
import app.omnivore.omnivore.ui.savedItemViews.SavedItemContextMenu
|
||||
import app.omnivore.omnivore.ui.theme.OmnivoreTheme
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlin.math.roundToInt
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights
|
||||
import app.omnivore.omnivore.ui.components.LabelChipColors
|
||||
import app.omnivore.omnivore.ui.library.SavedItemFilter
|
||||
import app.omnivore.omnivore.ui.library.SearchField
|
||||
import app.omnivore.omnivore.ui.library.SearchViewContent
|
||||
import app.omnivore.omnivore.ui.library.TypeaheadSearchViewContent
|
||||
import app.omnivore.omnivore.ui.reader.WebReaderViewModel
|
||||
import app.omnivore.omnivore.ui.theme.md_theme_dark_outline
|
||||
import dev.jeziellago.compose.markdowntext.MarkdownText
|
||||
|
||||
|
||||
@AndroidEntryPoint
|
||||
class NotebookActivity: ComponentActivity() {
|
||||
val viewModel: NotebookViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val savedItemId = intent.getStringExtra("SAVED_ITEM_ID")
|
||||
|
||||
setContent {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val useDarkIcons = !isSystemInDarkTheme()
|
||||
|
||||
DisposableEffect(systemUiController, useDarkIcons) {
|
||||
systemUiController.setSystemBarsColor(
|
||||
color = Color.Black,
|
||||
darkIcons = false
|
||||
)
|
||||
|
||||
onDispose {}
|
||||
}
|
||||
|
||||
OmnivoreTheme {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = Color.Black)
|
||||
) {
|
||||
savedItemId?.let {
|
||||
NotebookView(
|
||||
savedItemId = savedItemId,
|
||||
viewModel = viewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // animate the view up when keyboard appears
|
||||
// WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
// val rootView = findViewById<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)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NotebookView(savedItemId: String, viewModel: NotebookViewModel) {
|
||||
val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
|
||||
val savedItem = viewModel.getLibraryItemById(savedItemId).observeAsState()
|
||||
|
||||
// var isMenuExpanded by remember { mutableStateOf(false) }
|
||||
// var showWebPreferencesDialog by remember { mutableStateOf(false ) }
|
||||
|
||||
|
||||
val noteMarkdown = "This is some *markdown* for a note."
|
||||
|
||||
OmnivoreTheme() {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Notebook") },
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
),
|
||||
navigationIcon = {
|
||||
IconButton(onClick = {
|
||||
onBackPressedDispatcher?.onBackPressed()
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = androidx.compose.material.icons.Icons.Filled.ArrowBack,
|
||||
modifier = Modifier,
|
||||
contentDescription = "Back"
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
savedItem.value?.let {
|
||||
ArticleNotes(it)
|
||||
HighlightsList(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ArticleNotes(item: SavedItemWithLabelsAndHighlights) {
|
||||
val notes = item.highlights?.filter { it.type == "NOTE" } ?: listOf()
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
Column(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 15.dp)
|
||||
.padding(top = 40.dp, bottom = 10.dp)
|
||||
) {
|
||||
Text("Article Notes")
|
||||
Divider(modifier = Modifier.padding(bottom= 15.dp))
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.padding(0.dp, end = 15.dp)
|
||||
.fillMaxWidth(),
|
||||
shape = androidx.compose.material.MaterialTheme.shapes.medium,
|
||||
color = MaterialTheme.colorScheme.surfaceVariant
|
||||
) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
// modifier = Modifier
|
||||
// .fillMaxSize()
|
||||
) {
|
||||
items(notes) { note ->
|
||||
MarkdownText(
|
||||
// modifier = Modifier.padding(paddingValues),
|
||||
markdown = note.annotation ?: "",
|
||||
fontSize = 12.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (notes.isEmpty()) {
|
||||
Text(
|
||||
text = "Add Notes...",
|
||||
style = androidx.compose.material.MaterialTheme.typography.subtitle2,
|
||||
modifier = Modifier.padding(vertical = 10.dp, horizontal = 10.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HighlightsList(item: SavedItemWithLabelsAndHighlights) {
|
||||
val highlights = item.highlights?.filter { it.type == "HIGHLIGHT" } ?: listOf()
|
||||
val listState = rememberLazyListState()
|
||||
val yellowColor = colorResource(R.color.cta_yellow)
|
||||
|
||||
Column(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 15.dp)
|
||||
.padding(top = 40.dp)
|
||||
) {
|
||||
Text("Highlights")
|
||||
Divider(modifier = Modifier.padding(bottom= 10.dp))
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
items(highlights) { highlight ->
|
||||
var isMenuOpen by remember { mutableStateOf(false) }
|
||||
|
||||
Row(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.End)) {
|
||||
IconButton(onClick = { isMenuOpen = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = isMenuOpen,
|
||||
onDismissRequest = { isMenuOpen = false }
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = { Text("Copy") },
|
||||
onClick = {
|
||||
// actionHandler(it)
|
||||
// onDismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
highlight.quote?.let {
|
||||
Row(modifier = Modifier
|
||||
.padding(start = 2.dp, end = 15.dp)
|
||||
.fillMaxWidth()
|
||||
.drawWithCache {
|
||||
onDrawWithContent {
|
||||
// draw behind the content the vertical line on the left
|
||||
drawLine(
|
||||
color = yellowColor,
|
||||
start = Offset.Zero,
|
||||
end = Offset(0f, this.size.height),
|
||||
strokeWidth = 10f
|
||||
)
|
||||
|
||||
// draw the content
|
||||
drawContent()
|
||||
}
|
||||
}) {
|
||||
|
||||
MarkdownText(
|
||||
modifier = Modifier
|
||||
.padding(start = 15.dp, end = 15.dp),
|
||||
markdown = it,
|
||||
fontSize = 12.sp,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
highlight.annotation?.let {
|
||||
MarkdownText(
|
||||
// modifier = Modifier.padding(paddingValues),
|
||||
markdown = it,
|
||||
fontSize = 12.sp,
|
||||
)
|
||||
} ?: run {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.padding(0.dp, end = 15.dp, top = 15.dp, bottom = 30.dp)
|
||||
.fillMaxWidth(),
|
||||
shape = androidx.compose.material.MaterialTheme.shapes.medium,
|
||||
color = MaterialTheme.colorScheme.surfaceVariant
|
||||
) {
|
||||
Row {
|
||||
Text(
|
||||
text = "Add Notes...",
|
||||
style = androidx.compose.material.MaterialTheme.typography.subtitle2,
|
||||
modifier = Modifier.padding(vertical = 10.dp, horizontal = 10.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (highlights.isEmpty()) {
|
||||
Text(
|
||||
text = "You have not added any highlights to this page.",
|
||||
style = androidx.compose.material.MaterialTheme.typography.subtitle2,
|
||||
modifier = Modifier.padding(vertical = 10.dp, horizontal = 10.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package app.omnivore.omnivore.ui.notebook
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import app.omnivore.omnivore.DatastoreRepository
|
||||
import app.omnivore.omnivore.dataService.DataService
|
||||
import app.omnivore.omnivore.networking.Networker
|
||||
import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights
|
||||
import app.omnivore.omnivore.ui.library.SavedItemViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class NotebookViewModel @Inject constructor(
|
||||
private val networker: Networker,
|
||||
private val dataService: DataService,
|
||||
private val datastoreRepo: DatastoreRepository
|
||||
): ViewModel() {
|
||||
|
||||
fun getLibraryItemById(savedItemId: String): LiveData<SavedItemWithLabelsAndHighlights> {
|
||||
return dataService.db.savedItemDao().getLibraryItemById(savedItemId)
|
||||
}
|
||||
}
|
||||
@ -26,6 +26,7 @@ import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -33,8 +34,6 @@ import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import app.omnivore.omnivore.MainActivity
|
||||
import app.omnivore.omnivore.R
|
||||
import app.omnivore.omnivore.ui.components.WebReaderLabelsSelectionSheet
|
||||
@ -44,6 +43,8 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlin.math.roundToInt
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import app.omnivore.omnivore.Routes
|
||||
import app.omnivore.omnivore.ui.notebook.NotebookActivity
|
||||
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -55,7 +56,6 @@ class WebReaderLoadingContainerActivity: ComponentActivity() {
|
||||
val requestID = intent.getStringExtra("SAVED_ITEM_REQUEST_ID")
|
||||
val slug = intent.getStringExtra("SAVED_ITEM_SLUG")
|
||||
|
||||
|
||||
setContent {
|
||||
val systemUiController = rememberSystemUiController()
|
||||
val useDarkIcons = !isSystemInDarkTheme()
|
||||
@ -122,6 +122,8 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o
|
||||
webReaderViewModel.maxToolbarHeightPx = with(LocalDensity.current) { maxToolbarHeight.roundToPx().toFloat() }
|
||||
webReaderViewModel.loadItem(slug = slug, requestID = requestID)
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
val styledContent = webReaderParams?.let {
|
||||
val webReaderContent = WebReaderContent(
|
||||
preferences = webReaderViewModel.storedWebPreferences(isSystemInDarkTheme()),
|
||||
@ -171,6 +173,18 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, o
|
||||
)
|
||||
}
|
||||
}
|
||||
webReaderParams?.let {
|
||||
IconButton(onClick = {
|
||||
val intent = Intent(context, NotebookActivity::class.java)
|
||||
intent.putExtra("SAVED_ITEM_ID", it.item.savedItemId)
|
||||
context.startActivity(intent)
|
||||
}) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.notebook),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = { showWebPreferencesDialog = true }) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.format_letter_case),
|
||||
|
||||
9
android/Omnivore/app/src/main/res/drawable/notebook.xml
Normal file
9
android/Omnivore/app/src/main/res/drawable/notebook.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<path
|
||||
android:pathData="M184,112a8,8 0,0 1,-8 8L112,120a8,8 0,0 1,0 -16h64A8,8 0,0 1,184 112ZM176,136L112,136a8,8 0,0 0,0 16h64a8,8 0,0 0,0 -16ZM224,48L224,208a16,16 0,0 1,-16 16L48,224a16,16 0,0 1,-16 -16L32,48A16,16 0,0 1,48 32L208,32A16,16 0,0 1,224 48ZM48,208L72,208L72,48L48,48ZM208,208L208,48L88,48L88,208L208,208Z"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
||||
@ -10,4 +10,8 @@
|
||||
<color name="gray_EBEBEB">#EBEBEB</color>
|
||||
|
||||
<color name="yellow_E6E4BF">#E6E4BF</color>
|
||||
<color name="cta_yellow">#F8D457</color>
|
||||
|
||||
<!-- <color name="F8F8F8">#F8F8F8</color>-->
|
||||
<!-- <color name="2E2E2B">#2E2E2B</color>-->
|
||||
</resources>
|
||||
|
||||
@ -13,6 +13,7 @@ dependencyResolutionManagement {
|
||||
maven {
|
||||
url = uri("https://customers.pspdfkit.com/maven")
|
||||
}
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
rootProject.name = "Omnivore"
|
||||
|
||||
Reference in New Issue
Block a user