Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): long press on an item to show context menu #613

Merged
merged 12 commits into from
Mar 10, 2024
6 changes: 3 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
)
}
}
}
Loading
Loading