Skip to content

Commit

Permalink
feat(ui): long press on an item to show context menu (#613)
Browse files Browse the repository at this point in the history
* feat(ui): long press on an item to show context menu

* feat(ui): add handy enter-exit transition to drop down menu

* feat(ui): implement share action

* feat(ui): polish the enter/exit transition

* fix(ui): RTL walkaround

* feat(ui): dropdown menu style tweaks

* feat(ui): mark above as read & mark below as read

* feat(fever): update read status by id set

* fix: use `batchMarkAsRead`

* fix: disable `onMarkAboveAsRead` for the first item
  • Loading branch information
JunkFood02 committed Mar 10, 2024
1 parent db65c3d commit 53523e4
Show file tree
Hide file tree
Showing 12 changed files with 939 additions and 91 deletions.
6 changes: 3 additions & 3 deletions app/build.gradle
Expand Up @@ -155,9 +155,9 @@ dependencies {
kapt "androidx.room:room-compiler:$room"

// https://developer.android.com/jetpack/androidx/releases/paging
implementation "androidx.paging:paging-common:$paging"
implementation "androidx.paging:paging-runtime:$paging"
implementation "androidx.paging:paging-compose:1.0.0-alpha14"
implementation "androidx.paging:paging-common-ktx:$paging"
implementation "androidx.paging:paging-runtime-ktx:$paging"
implementation "androidx.paging:paging-compose:$paging"

// https://developer.android.com/jetpack/androidx/releases/paging
implementation "androidx.browser:browser:1.5.0"
Expand Down
Expand Up @@ -8,6 +8,8 @@ import androidx.work.WorkManager
import com.rometools.rome.feed.synd.SyndFeed
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext
import me.ash.reader.R
Expand Down Expand Up @@ -323,6 +325,7 @@ class FeverRssService @Inject constructor(
}
}


override suspend fun batchMarkAsRead(articleIds: Set<String>, isUnread: Boolean) {
super.batchMarkAsRead(articleIds, isUnread)
val feverAPI = getFeverAPI()
Expand Down
Expand Up @@ -108,15 +108,17 @@ class GoogleReaderRssService @Inject constructor(
destCategoryId = groupId.dollarLast(),
destFeedName = searchedFeed.title!!
)
feedDao.insert(Feed(
id = accountId.spacerDollar(feedId),
name = searchedFeed.title!!,
url = feedLink,
groupId = groupId,
accountId = accountId,
isNotification = isNotification,
isFullContent = isFullContent,
))
feedDao.insert(
Feed(
id = accountId.spacerDollar(feedId),
name = searchedFeed.title!!,
url = feedLink,
groupId = groupId,
accountId = accountId,
isNotification = isNotification,
isFullContent = isFullContent,
)
)
// TODO: When users need to subscribe to multiple feeds continuously, this makes them uncomfortable.
// It is necessary to make syncWork support synchronizing individual specified feeds.
// super.doSyncOneTime()
Expand Down Expand Up @@ -233,11 +235,13 @@ class GoogleReaderRssService @Inject constructor(

// Handle folders
groupDao.insertOrUpdate(
listOf(Group(
id = groupId,
name = category.label!!,
accountId = accountId,
))
listOf(
Group(
id = groupId,
name = category.label!!,
accountId = accountId,
)
)
)
groupIds.add(groupId)

Expand Down Expand Up @@ -418,8 +422,10 @@ class GoogleReaderRssService @Inject constructor(
fullContent = it.summary?.content ?: "",
img = rssHelper.findImg(it.summary?.content ?: ""),
link = findArticleURL(it),
feedId = accountId.spacerDollar(it.origin?.streamId?.ofFeedStreamIdToId()
?: feedIds.first()),
feedId = accountId.spacerDollar(
it.origin?.streamId?.ofFeedStreamIdToId()
?: feedIds.first()
),
accountId = accountId,
isUnread = unreadIds.contains(articleId),
isStarred = starredIds.contains(articleId),
Expand Down Expand Up @@ -448,10 +454,12 @@ class GoogleReaderRssService @Inject constructor(
if (before == null) {
articleDao.queryMetadataByGroupIdWhenIsUnread(accountId, groupId, !isUnread)
} else {
articleDao.queryMetadataByGroupIdWhenIsUnread(accountId,
articleDao.queryMetadataByGroupIdWhenIsUnread(
accountId,
groupId,
!isUnread,
before)
before
)
}.map { it.id.dollarLast() }
}

Expand Down
@@ -0,0 +1,96 @@
package me.ash.reader.ui.component.menu

import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
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
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties

/**
* <a href="https://m3.material.io/components/menus/overview" class="external" target="_blank">Material Design dropdown menu</a>.
*
* Menus display a list of choices on a temporary surface. They appear when users interact with a
* button, action, or other control.
*
* ![Dropdown menu image](https://developer.android.com/images/reference/androidx/compose/material3/menu.png)
*
* A [DropdownMenu] behaves similarly to a [Popup], and will use the position of the parent layout
* to position itself on screen. Commonly a [DropdownMenu] will be placed in a [Box] with a sibling
* that will be used as the 'anchor'. Note that a [DropdownMenu] by itself will not take up any
* space in a layout, as the menu is displayed in a separate window, on top of other content.
*
* The [content] of a [DropdownMenu] will typically be [DropdownMenuItem]s, as well as custom
* content. Using [DropdownMenuItem]s will result in a menu that matches the Material
* specification for menus. Also note that the [content] is placed inside a scrollable [Column],
* so using a [LazyColumn] as the root layout inside [content] is unsupported.
*
* [onDismissRequest] will be called when the menu should close - for example when there is a
* tap outside the menu, or when the back key is pressed.
*
* [DropdownMenu] changes its positioning depending on the available space, always trying to be
* fully visible. Depending on layout direction, first it will try to align its start to the start
* of its parent, then its end to the end of its parent, and then to the edge of the window.
* Vertically, it will try to align its top to the bottom of its parent, then its bottom to top of
* its parent, and then to the edge of the window.
*
* An [offset] can be provided to adjust the positioning of the menu for cases when the layout
* bounds of its parent do not coincide with its visual bounds.
*
*
* @param expanded whether the menu is expanded or not
* @param onDismissRequest called when the user requests to dismiss the menu, such as by tapping
* outside the menu's bounds
* @param modifier [Modifier] to be applied to the menu's content
* @param offset [DpOffset] from the original position of the menu. The offset respects the
* [LayoutDirection], so the offset's x position will be added in LTR and subtracted in RTL.
* @param scrollState a [ScrollState] to used by the menu's content for items vertical scrolling
* @param properties [PopupProperties] for further customization of this popup's behavior
* @param content the content of this dropdown menu, typically a [DropdownMenuItem]
*/
@Composable
fun AnimatedDropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
offset: DpOffset = DpOffset(0.dp, 0.dp),
scrollState: ScrollState = rememberScrollState(),
properties: PopupProperties = PopupProperties(focusable = true),
content: @Composable ColumnScope.() -> Unit
) {
val expandedState = remember { MutableTransitionState(false) }
expandedState.targetState = expanded

if (expandedState.currentState || expandedState.targetState || !expandedState.isIdle) {
val density = LocalDensity.current
val popupPositionProvider = remember(offset, density) {
DropdownMenuPositionProvider(
offset,
density
)
}
Popup(
onDismissRequest = onDismissRequest,
popupPositionProvider = popupPositionProvider,
properties = properties
) {
DropdownMenuContent(
expandedState = expandedState,
scrollState = scrollState,
modifier = modifier,
content = content
)
}
}
}

0 comments on commit 53523e4

Please sign in to comment.