diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle index a8c001e18..25258b9bb 100644 --- a/android/Omnivore/app/build.gradle +++ b/android/Omnivore/app/build.gradle @@ -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 { diff --git a/android/Omnivore/app/src/main/AndroidManifest.xml b/android/Omnivore/app/src/main/AndroidManifest.xml index 3983989ed..a721ffdc7 100644 --- a/android/Omnivore/app/src/main/AndroidManifest.xml +++ b/android/Omnivore/app/src/main/AndroidManifest.xml @@ -57,5 +57,10 @@ android:exported="true" android:theme="@style/Theme.Omnivore"/> + + diff --git a/android/Omnivore/app/src/main/assets/test.pdf b/android/Omnivore/app/src/main/assets/test.pdf deleted file mode 100644 index 2887e5b76..000000000 Binary files a/android/Omnivore/app/src/main/assets/test.pdf and /dev/null differ diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Routes.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Routes.kt index 6608a30f6..01b4b9a6f 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Routes.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/Routes.kt @@ -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") } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt index 38cd552d6..eff79b6f0 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt @@ -162,6 +162,22 @@ interface SavedItemDao { ) fun getLibraryLiveDataSortedByRecentlyPublished(archiveFilter: Int): LiveData> + @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 + @Transaction @Query( "SELECT ${SavedItemQueryConstants.libraryColumns} " + diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt new file mode 100644 index 000000000..200688191 --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookView.kt @@ -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(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) + ) + } + } +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt new file mode 100644 index 000000000..e1095d1ab --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/notebook/NotebookViewModel.kt @@ -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 { + return dataService.db.savedItemDao().getLibraryItemById(savedItemId) + } +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index 5230b381e..7d64466ce 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -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), diff --git a/android/Omnivore/app/src/main/res/drawable/notebook.xml b/android/Omnivore/app/src/main/res/drawable/notebook.xml new file mode 100644 index 000000000..c39b20458 --- /dev/null +++ b/android/Omnivore/app/src/main/res/drawable/notebook.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/Omnivore/app/src/main/res/values/colors.xml b/android/Omnivore/app/src/main/res/values/colors.xml index d81888f7f..fdb6f43db 100644 --- a/android/Omnivore/app/src/main/res/values/colors.xml +++ b/android/Omnivore/app/src/main/res/values/colors.xml @@ -10,4 +10,8 @@ #EBEBEB #E6E4BF + #F8D457 + + + diff --git a/android/Omnivore/settings.gradle b/android/Omnivore/settings.gradle index 8a45271fe..d3111f0f7 100644 --- a/android/Omnivore/settings.gradle +++ b/android/Omnivore/settings.gradle @@ -13,6 +13,7 @@ dependencyResolutionManagement { maven { url = uri("https://customers.pspdfkit.com/maven") } + maven { url 'https://jitpack.io' } } } rootProject.name = "Omnivore"