Skip to content
This repository was archived by the owner on Sep 10, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Pocket/src/main/java/com/pocket/app/list/MyListFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ class MyListFragment : AbsPocketFragment() {

private fun setupNotesList() {
binding.notes.setContent {
Notes()
Notes(this::findNavController)
}
}

Expand Down
214 changes: 214 additions & 0 deletions Pocket/src/main/java/com/pocket/app/list/notes/NoteDetails.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package com.pocket.app.list.notes

import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.viewinterop.AndroidViewBinding
import androidx.fragment.compose.content
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.ideashower.readitlater.R
import com.ideashower.readitlater.databinding.ViewNoteDetailsContentBinding
import com.pocket.app.App
import com.pocket.data.models.Note
import com.pocket.repository.NotesRepository
import com.pocket.sdk.util.AbsPocketFragment
import com.pocket.sdk.util.MarkdownFormatter
import com.pocket.ui.view.AppBar
import com.pocket.ui.view.button.PocketIconButton
import com.pocket.ui.view.button.UpIcon
import com.pocket.ui.view.themed.PocketTheme
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class NoteDetailsViewModel
@Inject constructor(
private val notesRepository: NotesRepository,
) : ViewModel() {
fun getNote(id: Note.Id): NoteDetailsUiState {
return notesRepository.getNote(id)
?.let {
NoteDetailsUiState.Note(
it.title,
it.doc.value,
)
}
?: NoteDetailsUiState.Error
}
}

sealed class NoteDetailsUiState {
data class Note(
val title: String?,
val content: String,
) : NoteDetailsUiState()
data object Error : NoteDetailsUiState()
}

@AndroidEntryPoint
class NoteDetailsFragment : AbsPocketFragment() {
private val args: NoteDetailsFragmentArgs by navArgs()

override fun onCreateViewImpl(
inflater: LayoutInflater?,
container: ViewGroup?,
savedInstanceState: Bundle?,
) = content {
PocketTheme {
NoteDetailsScreen(
noteId = Note.Id(args.noteId),
onUpClick = { findNavController().navigateUp() },
)
}
}
}

@Composable
fun NoteDetailsScreen(
noteId: Note.Id,
viewModel: NoteDetailsViewModel = viewModel(),
onUpClick: () -> Unit,
) {
NoteDetailsScreen(
viewModel.getNote(noteId),
onUpClick,
)
}

@Composable
private fun NoteDetailsScreen(
note: NoteDetailsUiState,
onUpClick: () -> Unit,
) {
Scaffold(
topBar = {
AppBar(
navigationIcon = {
PocketIconButton(onClick = onUpClick) {
UpIcon()
}
},
)
}
) { contentPadding ->
when (note) {
is NoteDetailsUiState.Note -> {
NoteDetails(
note.title,
note.content,
Modifier
.consumeWindowInsets(contentPadding)
.padding(contentPadding),
)
}
NoteDetailsUiState.Error -> {
NoteDetailsError(
Modifier
.consumeWindowInsets(contentPadding)
.padding(contentPadding)
.fillMaxSize(),
)
}
}
}
}

@Composable
private fun NoteDetails(
title: String?,
markdownContent: String,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val markdownFormatter = remember(context) { MarkdownFormatter(context, App::viewUrl) }
val content = remember(markdownContent) { markdownFormatter.format(markdownContent) }

Column(
modifier.verticalScroll(rememberScrollState()),
) {
Spacer(Modifier.height(PocketTheme.dimensions.spaceMedium))
if (title != null) {
Text(
title,
Modifier.padding(horizontal = PocketTheme.dimensions.sideGrid),
style = PocketTheme.typography.h7,
)
}
Spacer(Modifier.height(PocketTheme.dimensions.spaceMedium))
AndroidViewBinding(
ViewNoteDetailsContentBinding::inflate,
Modifier.padding(horizontal = PocketTheme.dimensions.sideGrid),
) {
root.text = content
root.setMovementMethodForLinks(true)
}
Spacer(Modifier.height(PocketTheme.dimensions.spaceMedium))
}
}

@Composable
private fun NoteDetailsError(modifier: Modifier = Modifier) {
Box(
modifier,
contentAlignment = Alignment.Center,
) {
Text(
stringResource(R.string.dg_unexpected_m),
Modifier
.padding(horizontal = PocketTheme.dimensions.sideGrid),
textAlign = TextAlign.Center,
style = PocketTheme.typography.h6,
)
}
}

@Preview
@Composable
private fun NoteDetailsPreview() {
val markdown = """
Plain text
Look at how **bold** this is
Let me *emphasise* this
An [example](https://example.com) link
> Move *things* and **break** fast
> and break lines
# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6
""".trimIndent()
PocketTheme {
NoteDetailsScreen(
note = NoteDetailsUiState.Note(
title = "Optional note title",
content = markdown,
),
onUpClick = {},
)
}
}
42 changes: 37 additions & 5 deletions Pocket/src/main/java/com/pocket/app/list/notes/Notes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ package com.pocket.app.list.notes
import android.text.Spanned
import android.text.format.DateFormat
import androidx.compose.animation.Crossfade
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.core.rememberTransition
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand All @@ -22,6 +26,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand All @@ -33,6 +38,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidViewBinding
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.LoadState
import androidx.paging.LoadStates
import androidx.paging.LoadType
Expand All @@ -43,6 +49,7 @@ import androidx.paging.compose.itemKey
import com.ideashower.readitlater.R
import com.ideashower.readitlater.databinding.ViewNoteRowContentBinding
import com.pocket.app.App
import com.pocket.app.list.MyListFragmentDirections
import com.pocket.app.list.MyListViewModel
import com.pocket.data.models.Note
import com.pocket.sdk.api.value.MarkdownString
Expand All @@ -64,46 +71,66 @@ import java.util.Date
/** Notes view embedded on the Saves tab when the Notes filter is selected. */
@Composable
fun Notes(
findNavController: () -> NavController,
myListViewModel: MyListViewModel = viewModel(),
viewModel: NotesViewModel = viewModel(),
) {
viewModel.initialize(myListViewModel)
LaunchedEffect(viewModel, myListViewModel) {
viewModel.initialize(myListViewModel)
}
LaunchedEffect(findNavController, viewModel) {
viewModel.events.collect { event ->
when (event) {
is NotesViewModel.Event.GoToNoteDetails -> {
findNavController().navigate(MyListFragmentDirections.goToNoteDetails(event.noteId.value))
}
}
}
}

PocketTheme {
Notes(
lazyPagingNotes = viewModel.notes.collectAsLazyPagingItems(),
onNoteClick = viewModel::onNoteClicked,
onCreateNoteClick = { /* TODO(notes): POCKET-10881 */ },
)
}
}

@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun Notes(
lazyPagingNotes: LazyPagingItems<NoteUiState>,
onNoteClick: (Note.Id) -> Unit,
onCreateNoteClick: () -> Unit,
) {
val state = when {
val state = remember { MutableTransitionState(NotesState.Initial) }
state.targetState = when {
lazyPagingNotes.itemCount > 0 -> NotesState.List
lazyPagingNotes.loadState.isIdle -> NotesState.Empty
lazyPagingNotes.loadState.hasError -> NotesState.Error
else -> NotesState.Loading
}
Crossfade(state) {
val transition = rememberTransition(state)
transition.Crossfade {
when (it) {
NotesState.List -> NotesList(lazyPagingNotes)
NotesState.Initial -> { /* Initial blank state. */}
NotesState.List -> NotesList(lazyPagingNotes, onNoteClick)
NotesState.Empty -> NotesEmpty(onCreateNoteClick)
NotesState.Error -> NotesError(Modifier.fillMaxSize())
NotesState.Loading -> NotesLoading()
}
}
}
private enum class NotesState {
List, Empty, Error, Loading
Initial, List, Empty, Error, Loading
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun NotesList(
lazyPagingNotes: LazyPagingItems<NoteUiState>,
onNoteClick: (Note.Id) -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
Expand Down Expand Up @@ -131,6 +158,7 @@ private fun NotesList(
},
remember(note.date) { note.date.formatWith(dateFormat) },
Modifier
.clickable { onNoteClick(note.id) }
.padding(20.dp)
.animateItem(),
)
Expand Down Expand Up @@ -303,6 +331,7 @@ private fun Loaded() {
arbitraryDay.minusDays(it.toLong() * 40).toInstant(),
)
}.asLazyPagingItems(),
onNoteClick = {},
onCreateNoteClick = {},
)
}
Expand All @@ -314,6 +343,7 @@ private fun Empty() {
PocketTheme {
Notes(
lazyPagingNotes = emptyList<NoteUiState>().asLazyPagingItems(),
onNoteClick = {},
onCreateNoteClick = {},
)
}
Expand All @@ -326,6 +356,7 @@ private fun Error() {
Notes(
lazyPagingNotes = emptyList<NoteUiState>()
.asLazyPagingItems(refresh = LoadState.Error(RuntimeException())),
onNoteClick = {},
onCreateNoteClick = {},
)
}
Expand All @@ -338,6 +369,7 @@ private fun Loading() {
Notes(
lazyPagingNotes = emptyList<NoteUiState>()
.asLazyPagingItems(refresh = LoadState.Loading),
onNoteClick = {},
onCreateNoteClick = {},
)
}
Expand Down
Loading