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

Show active source name in home top appbar #456

Merged
merged 1 commit into from
Apr 17, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ val DeTwineStrings =
postsAll = "Alle artikel",
postsUnread = "Ungelesen",
postsToday = "Heute",
postsLast24Hours = "24 Stunden",
postsLast24Hours = "Letzte 24 Stunden",
openSource = "Open Source",
openSourceDesc =
"Twine basiert auf Open-Source-Technologien und ist völlig kostenlos. Den Quellcode von Twine und einigen meiner anderen beliebten Projekte finden Sie auf GitHub. Klicken Sie hier, um dorthin zu gelangen.",
Expand Down Expand Up @@ -153,4 +153,5 @@ val DeTwineStrings =
},
actionGroupsTooltip = "Gruppen können nicht innerhalb anderer Gruppen sein.",
groupAddNew = "Add new",
appBarAllFeeds = "All feeds",
)
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ val EnTwineStrings =
postsAll = "All articles",
postsUnread = "Unread",
postsToday = "Today",
postsLast24Hours = "24 hours",
postsLast24Hours = "Last 24 hours",
openSource = "Support Open Source",
openSourceDesc =
"Twine is an open source project and is available for free to use. Click here to know more on how to support this project or, view source code of Twine or some of my other popular projects.",
Expand Down Expand Up @@ -160,4 +160,5 @@ val EnTwineStrings =
},
actionGroupsTooltip = "Groups cannot be inside other groups.",
groupAddNew = "Add new",
appBarAllFeeds = "All feeds",
)
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ val TrTwineStrings =
postsAll = "Tümü makaleler",
postsUnread = "Okunmamış",
postsToday = "Bugün",
postsLast24Hours = "24 saat",
postsLast24Hours = "Son 24 saat",
openSource = "Açık kaynağı destekleyin",
openSourceDesc =
"Twine açık kaynaklı bir projedir ve ücretsiz olarak kullanılabilir. Bu projeyi nasıl destekleyeceğiniz hakkında daha fazla bilgi edinmek için buraya tıklayın veya Twine'ın veya diğer popüler projelerimden bazılarının kaynak kodunu görüntüleyin.",
Expand Down Expand Up @@ -149,4 +149,5 @@ val TrTwineStrings =
},
actionGroupsTooltip = "Gruplar başka grupların içinde olamaz.",
groupAddNew = "Add new",
appBarAllFeeds = "All feeds",
)
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ data class TwineStrings(
val feedGroupFeeds: (Int) -> String,
val actionGroupsTooltip: String,
val groupAddNew: String,
val appBarAllFeeds: String,
)

object Locales {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ internal fun HomeScreen(homePresenter: HomePresenter, modifier: Modifier = Modif
scaffoldState = bottomSheetScaffoldState,
topBar = {
HomeTopAppBar(
hasFeeds = state.hasFeeds ?: false,
source = state.activeSource,
postsType = state.postsType,
listState = listState,
onSearchClicked = { homePresenter.dispatch(HomeEvent.SearchClicked) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,31 @@
package dev.sasikanth.rss.reader.home.ui

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.rounded.MoreVert
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.derivedStateOf
Expand All @@ -48,13 +51,18 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.dp
import dev.sasikanth.rss.reader.components.DropdownMenu
import dev.sasikanth.rss.reader.components.DropdownMenuItem
import dev.sasikanth.rss.reader.components.image.AsyncImage
import dev.sasikanth.rss.reader.core.model.local.Feed
import dev.sasikanth.rss.reader.core.model.local.FeedGroup
import dev.sasikanth.rss.reader.core.model.local.Source
import dev.sasikanth.rss.reader.feeds.ui.FeedGroupIconGrid
import dev.sasikanth.rss.reader.resources.icons.Bookmarks
import dev.sasikanth.rss.reader.resources.icons.RSS
import dev.sasikanth.rss.reader.resources.icons.Tune
import dev.sasikanth.rss.reader.resources.icons.TwineIcons
import dev.sasikanth.rss.reader.resources.strings.LocalStrings
Expand All @@ -64,7 +72,7 @@ private const val APP_BAR_OPAQUE_THRESHOLD = 200f

@Composable
internal fun HomeTopAppBar(
hasFeeds: Boolean,
source: Source?,
postsType: PostsType,
listState: LazyListState,
modifier: Modifier = Modifier,
Expand Down Expand Up @@ -93,16 +101,17 @@ internal fun HomeTopAppBar(
.windowInsetsPadding(
WindowInsets.systemBars.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
)
.padding(start = 12.dp, end = 12.dp, top = 16.dp, bottom = 16.dp),
.padding(horizontal = 8.dp, vertical = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (!hasFeeds) {
AppName()
} else {
PostsTypeSelector(postsType = postsType, onPostTypeChanged = onPostTypeChanged)
}
PostsFilter(
modifier = Modifier.weight(1f),
source = source,
postsType = postsType,
onPostTypeChanged = onPostTypeChanged
)

Spacer(Modifier.weight(1f))
Spacer(Modifier.requiredWidth(16.dp))

IconButton(
onClick = onSearchClicked,
Expand All @@ -114,80 +123,134 @@ internal fun HomeTopAppBar(
)
}

IconButton(
onClick = onBookmarksClicked,
) {
Icon(
imageVector = TwineIcons.Bookmarks,
contentDescription = LocalStrings.current.bookmarks,
tint = AppTheme.colorScheme.tintedForeground
)
}

OverflowMenu(onSettingsClicked)
OverflowMenu(
onSettingsClicked = onSettingsClicked,
onBookmarksClicked = onBookmarksClicked,
)
}
}

@Composable
fun PostsTypeSelector(
fun PostsFilter(
modifier: Modifier = Modifier,
source: Source?,
postsType: PostsType = PostsType.ALL,
onPostTypeChanged: (PostsType) -> Unit,
) {
var showDropdown by remember { mutableStateOf(false) }
val title = getPostTypeLabel(postsType)
val postsTypeLabel = getPostTypeLabel(postsType)

Row(
modifier = modifier.clip(MaterialTheme.shapes.large).clickable { showDropdown = true },
verticalAlignment = Alignment.CenterVertically
) {
SourceIcon(source)

Column(modifier = Modifier.padding(start = 16.dp, end = 8.dp)) {
val sourceLabel =
when (source) {
is FeedGroup -> source.name
is Feed -> source.name
else -> LocalStrings.current.appBarAllFeeds
}

Box {
TextButton(
onClick = { showDropdown = true },
modifier = modifier,
) {
Text(
modifier = Modifier.align(Alignment.CenterVertically),
text = title,
color = Color.White,
style = MaterialTheme.typography.titleLarge
text = sourceLabel,
style = MaterialTheme.typography.titleLarge,
color = AppTheme.colorScheme.textEmphasisHigh,
)

Spacer(Modifier.width(4.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = postsTypeLabel,
style = MaterialTheme.typography.bodyMedium,
color = AppTheme.colorScheme.textEmphasisHigh,
)

Icon(
modifier = Modifier.align(Alignment.CenterVertically),
imageVector = Icons.Filled.ArrowDropDown,
contentDescription = null,
tint = Color.White
)
Icon(
imageVector = Icons.Filled.ExpandMore,
contentDescription = null,
modifier = Modifier.requiredSize(20.dp),
tint = AppTheme.colorScheme.tintedForeground,
)
}
}
}

DropdownMenu(
modifier = Modifier.requiredWidth(158.dp),
expanded = showDropdown,
onDismissRequest = { showDropdown = false },
) {
PostsType.entries.forEach { type ->
val label = getPostTypeLabel(type)
val color =
if (postsType == type) {
AppTheme.colorScheme.tintedSurface
DropdownMenu(
modifier = Modifier.requiredWidth(158.dp),
expanded = showDropdown,
onDismissRequest = { showDropdown = false },
) {
PostsType.entries.forEach { type ->
val label = getPostTypeLabel(type)
val color =
if (postsType == type) {
AppTheme.colorScheme.tintedSurface
} else {
Color.Unspecified
}
val labelColor =
if (postsType == type) {
AppTheme.colorScheme.onSurface
} else {
AppTheme.colorScheme.textEmphasisHigh
}

DropdownMenuItem(
onClick = {
onPostTypeChanged(type)
showDropdown = false
},
modifier = Modifier.background(color)
) {
Text(text = label, style = MaterialTheme.typography.bodyLarge, color = labelColor)
}
}
}
}

@Composable
private fun SourceIcon(source: Source?, modifier: Modifier = Modifier) {
Row(modifier) {
if (source != null) {
Spacer(Modifier.requiredWidth(16.dp))
}

when (source) {
is FeedGroup -> {
val iconSize =
if (source.feedIcons.size > 2) {
18.dp
} else {
Color.Unspecified
20.dp
}
val labelColor =
if (postsType == type) {
AppTheme.colorScheme.onSurface

val iconSpacing =
if (source.feedIcons.size > 2) {
4.dp
} else {
AppTheme.colorScheme.textEmphasisHigh
0.dp
}

DropdownMenuItem(
onClick = {
onPostTypeChanged(type)
showDropdown = false
},
modifier = Modifier.background(color)
) {
Text(text = label, style = MaterialTheme.typography.bodyLarge, color = labelColor)
}
FeedGroupIconGrid(
icons = source.feedIcons,
iconSize = iconSize,
iconShape = RoundedCornerShape(percent = 30),
horizontalArrangement = Arrangement.spacedBy(iconSpacing),
verticalArrangement = Arrangement.spacedBy(iconSpacing)
)
}
is Feed -> {
AsyncImage(
url = source.icon,
contentDescription = null,
backgroundColor = Color.White,
modifier = Modifier.clip(MaterialTheme.shapes.small).requiredSize(24.dp)
)
}
else -> {
// no-op
}
}
}
Expand All @@ -204,22 +267,7 @@ private fun getPostTypeLabel(type: PostsType) =
}

@Composable
private fun AppName(modifier: Modifier = Modifier) {
Row(modifier = modifier.padding(start = 12.dp), verticalAlignment = Alignment.CenterVertically) {
Text(
text = LocalStrings.current.appName,
color = Color.White,
style = MaterialTheme.typography.titleLarge
)

Spacer(Modifier.width(4.dp))

Icon(imageVector = TwineIcons.RSS, contentDescription = null, tint = Color.White)
}
}

@Composable
private fun OverflowMenu(onSettingsClicked: () -> Unit) {
private fun OverflowMenu(onBookmarksClicked: () -> Unit, onSettingsClicked: () -> Unit) {
Box {
var dropdownExpanded by remember { mutableStateOf(false) }

Expand All @@ -235,6 +283,20 @@ private fun OverflowMenu(onSettingsClicked: () -> Unit) {

if (dropdownExpanded) {
DropdownMenu(expanded = dropdownExpanded, onDismissRequest = { dropdownExpanded = false }) {
DropdownMenuItem(
text = { Text(text = LocalStrings.current.bookmarks) },
leadingIcon = {
Icon(
imageVector = TwineIcons.Bookmarks,
contentDescription = LocalStrings.current.bookmarks
)
},
onClick = {
dropdownExpanded = false
onBookmarksClicked()
}
)

DropdownMenuItem(
text = { Text(text = LocalStrings.current.settings) },
leadingIcon = {
Expand Down