Skip to content

Commit cdd52a6

Browse files
authored
Merge pull request #152 from malkoG/timeline-language-filter
Add language filters to timelines
2 parents f9e1efd + 62be42c commit cdd52a6

13 files changed

Lines changed: 577 additions & 181 deletions

File tree

app/src/main/graphql/pub/hackers/android/operations.graphql

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ fragment SharedPostFields on Post {
126126
}
127127
}
128128

129-
query PublicTimeline($first: Int, $after: String, $before: String, $last: Int) {
130-
publicTimeline(first: $first, after: $after, before: $before, last: $last) {
129+
query PublicTimeline($first: Int, $after: String, $before: String, $last: Int, $languages: [Locale!]) {
130+
publicTimeline(first: $first, after: $after, before: $before, last: $last, languages: $languages) {
131131
edges {
132132
cursor
133133
node {
@@ -146,8 +146,8 @@ query PublicTimeline($first: Int, $after: String, $before: String, $last: Int) {
146146
}
147147
}
148148

149-
query LocalTimeline($first: Int, $after: String, $before: String, $last: Int) {
150-
publicTimeline(local: true, first: $first, after: $after, before: $before, last: $last) {
149+
query LocalTimeline($first: Int, $after: String, $before: String, $last: Int, $languages: [Locale!]) {
150+
publicTimeline(local: true, first: $first, after: $after, before: $before, last: $last, languages: $languages) {
151151
edges {
152152
cursor
153153
node {
@@ -166,8 +166,8 @@ query LocalTimeline($first: Int, $after: String, $before: String, $last: Int) {
166166
}
167167
}
168168

169-
query PersonalTimeline($first: Int, $after: String, $before: String, $last: Int) {
170-
personalTimeline(first: $first, after: $after, before: $before, last: $last) {
169+
query PersonalTimeline($first: Int, $after: String, $before: String, $last: Int, $languages: [Locale!]) {
170+
personalTimeline(first: $first, after: $after, before: $before, last: $last, languages: $languages) {
171171
edges {
172172
cursor
173173
lastSharer {
@@ -205,6 +205,10 @@ query Viewer {
205205
}
206206
}
207207

208+
query SuggestedFilterLanguages {
209+
suggestedFilterLanguages
210+
}
211+
208212
query Notifications($after: String) {
209213
viewer {
210214
id

app/src/main/graphql/pub/hackers/android/schema.graphqls

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1521,7 +1521,7 @@ type Query {
15211521

15221522
nodes(ids: [ID!]!): [Node]!
15231523

1524-
personalTimeline(after: String, before: String, first: Int, last: Int, local: Boolean = false, postType: PostType, withoutShares: Boolean = false): QueryPersonalTimelineConnection!
1524+
personalTimeline(after: String, before: String, first: Int, languages: [Locale!] = [], last: Int, local: Boolean = false, postType: PostType, withoutShares: Boolean = false): QueryPersonalTimelineConnection!
15251525

15261526
postByUrl(url: String!): Post
15271527

@@ -1539,6 +1539,8 @@ type Query {
15391539

15401540
searchPost(after: String, before: String, first: Int, languages: [Locale!] = [], last: Int, query: String!): QuerySearchPostConnection!
15411541

1542+
suggestedFilterLanguages: [Locale!]!
1543+
15421544
"""
15431545
Verify a signup token and return the signup info if valid.
15441546
"""

app/src/main/java/pub/hackers/android/data/paging/CursorPagingSource.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,22 @@ suspend fun HackersPubRepository.notificationsPage(after: String?) =
8989
getNotifications(after = after, refresh = (after == null))
9090
.map { CursorPage(it.notifications, it.endCursor, it.hasNextPage) }
9191

92-
suspend fun HackersPubRepository.personalTimelinePage(after: String?) =
93-
getPersonalTimeline(after = after, refresh = (after == null))
92+
suspend fun HackersPubRepository.personalTimelinePage(
93+
after: String?,
94+
languages: List<String> = emptyList(),
95+
) = getPersonalTimeline(after = after, refresh = (after == null), languages = languages)
9496
.map { CursorPage(it.posts, it.endCursor, it.hasNextPage, it.startCursor, it.hasPreviousPage) }
9597

96-
suspend fun HackersPubRepository.publicTimelinePage(after: String?) =
97-
getPublicTimeline(after = after, refresh = (after == null))
98+
suspend fun HackersPubRepository.publicTimelinePage(
99+
after: String?,
100+
languages: List<String> = emptyList(),
101+
) = getPublicTimeline(after = after, refresh = (after == null), languages = languages)
98102
.map { CursorPage(it.posts, it.endCursor, it.hasNextPage, it.startCursor, it.hasPreviousPage) }
99103

100-
suspend fun HackersPubRepository.localTimelinePage(after: String?) =
101-
getLocalTimeline(after = after, refresh = (after == null))
104+
suspend fun HackersPubRepository.localTimelinePage(
105+
after: String?,
106+
languages: List<String> = emptyList(),
107+
) = getLocalTimeline(after = after, refresh = (after == null), languages = languages)
102108
.map { CursorPage(it.posts, it.endCursor, it.hasNextPage, it.startCursor, it.hasPreviousPage) }
103109

104110
suspend fun HackersPubRepository.postRepliesPage(postId: String, after: String?) =

app/src/main/java/pub/hackers/android/data/repository/HackersPubRepository.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import pub.hackers.android.graphql.SearchObjectQuery
6868
import pub.hackers.android.graphql.SearchPostQuery
6969
import pub.hackers.android.graphql.SharePostMutation
7070
import pub.hackers.android.graphql.StartMediumUploadMutation
71+
import pub.hackers.android.graphql.SuggestedFilterLanguagesQuery
7172
import pub.hackers.android.graphql.UnblockActorMutation
7273
import pub.hackers.android.graphql.UnfollowActorMutation
7374
import pub.hackers.android.graphql.UnbookmarkPostMutation
@@ -101,6 +102,7 @@ class HackersPubRepository @Inject constructor(
101102
after: String? = null,
102103
before: String? = null,
103104
refresh: Boolean = false,
105+
languages: List<String> = emptyList(),
104106
): Result<TimelineResult> {
105107
return try {
106108
val backwards = before != null
@@ -110,6 +112,7 @@ class HackersPubRepository @Inject constructor(
110112
after = Optional.presentIfNotNull(if (backwards) null else after),
111113
before = Optional.presentIfNotNull(before),
112114
last = Optional.presentIfNotNull(if (backwards) 20 else null),
115+
languages = Optional.present(languages),
113116
)
114117
)
115118
.apply { if (refresh || backwards) fetchPolicy(FetchPolicy.NetworkOnly) }
@@ -142,6 +145,7 @@ class HackersPubRepository @Inject constructor(
142145
after: String? = null,
143146
before: String? = null,
144147
refresh: Boolean = false,
148+
languages: List<String> = emptyList(),
145149
): Result<TimelineResult> {
146150
return try {
147151
val backwards = before != null
@@ -151,6 +155,7 @@ class HackersPubRepository @Inject constructor(
151155
after = Optional.presentIfNotNull(if (backwards) null else after),
152156
before = Optional.presentIfNotNull(before),
153157
last = Optional.presentIfNotNull(if (backwards) 20 else null),
158+
languages = Optional.present(languages),
154159
)
155160
)
156161
.apply { if (refresh || backwards) fetchPolicy(FetchPolicy.NetworkOnly) }
@@ -183,6 +188,7 @@ class HackersPubRepository @Inject constructor(
183188
after: String? = null,
184189
before: String? = null,
185190
refresh: Boolean = false,
191+
languages: List<String> = emptyList(),
186192
): Result<TimelineResult> {
187193
return try {
188194
val backwards = before != null
@@ -192,6 +198,7 @@ class HackersPubRepository @Inject constructor(
192198
after = Optional.presentIfNotNull(if (backwards) null else after),
193199
before = Optional.presentIfNotNull(before),
194200
last = Optional.presentIfNotNull(if (backwards) 20 else null),
201+
languages = Optional.present(languages),
195202
)
196203
)
197204
.apply { if (refresh || backwards) fetchPolicy(FetchPolicy.NetworkOnly) }
@@ -226,6 +233,19 @@ class HackersPubRepository @Inject constructor(
226233
}
227234
}
228235

236+
suspend fun getSuggestedFilterLanguages(): Result<List<String>> {
237+
return try {
238+
val response = apolloClient.query(SuggestedFilterLanguagesQuery()).execute()
239+
if (response.hasErrors()) {
240+
Result.failure(Exception(response.errors?.firstOrNull()?.message ?: "Unknown error"))
241+
} else {
242+
Result.success(response.data?.suggestedFilterLanguages.orEmpty().map { it.toString() })
243+
}
244+
} catch (e: Exception) {
245+
Result.failure(e)
246+
}
247+
}
248+
229249
suspend fun getNotifications(after: String? = null, refresh: Boolean = false): Result<NotificationsResult> {
230250
return try {
231251
val response = apolloClient.query(NotificationsQuery(Optional.presentIfNotNull(after)))
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package pub.hackers.android.ui.components
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.PaddingValues
5+
import androidx.compose.foundation.layout.fillMaxWidth
6+
import androidx.compose.foundation.lazy.LazyRow
7+
import androidx.compose.foundation.lazy.items
8+
import androidx.compose.material3.FilterChip
9+
import androidx.compose.material3.FilterChipDefaults
10+
import androidx.compose.material3.Text
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.runtime.remember
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.platform.LocalConfiguration
15+
import androidx.compose.ui.res.stringResource
16+
import androidx.compose.ui.unit.dp
17+
import pub.hackers.android.R
18+
import pub.hackers.android.ui.theme.LocalAppColors
19+
import java.util.Locale
20+
21+
@Composable
22+
fun LanguageFilterRow(
23+
languages: List<String>,
24+
selectedLanguage: String?,
25+
onLanguageSelected: (String?) -> Unit,
26+
modifier: Modifier = Modifier,
27+
) {
28+
val configuration = LocalConfiguration.current
29+
val uiLocale = remember(configuration) {
30+
if (configuration.locales.size() > 0) {
31+
configuration.locales[0]
32+
} else {
33+
Locale.getDefault()
34+
}
35+
}
36+
val displayLanguages = remember(languages, selectedLanguage) {
37+
if (selectedLanguage != null && selectedLanguage !in languages) {
38+
listOf(selectedLanguage) + languages
39+
} else {
40+
languages
41+
}
42+
}
43+
44+
LazyRow(
45+
modifier = modifier.fillMaxWidth(),
46+
horizontalArrangement = Arrangement.spacedBy(8.dp),
47+
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 10.dp),
48+
) {
49+
item(key = "all") {
50+
LanguageFilterChip(
51+
label = stringResource(R.string.all_languages),
52+
selected = selectedLanguage == null,
53+
onClick = { onLanguageSelected(null) },
54+
)
55+
}
56+
items(
57+
items = displayLanguages,
58+
key = { it },
59+
) { language ->
60+
LanguageFilterChip(
61+
label = languageFilterLabel(language, uiLocale),
62+
selected = selectedLanguage == language,
63+
onClick = { onLanguageSelected(language) },
64+
)
65+
}
66+
}
67+
}
68+
69+
@Composable
70+
private fun LanguageFilterChip(
71+
label: String,
72+
selected: Boolean,
73+
onClick: () -> Unit,
74+
) {
75+
val colors = LocalAppColors.current
76+
FilterChip(
77+
selected = selected,
78+
onClick = onClick,
79+
label = { Text(label) },
80+
colors = FilterChipDefaults.filterChipColors(
81+
containerColor = colors.background,
82+
labelColor = colors.textBody,
83+
selectedContainerColor = colors.accent,
84+
selectedLabelColor = colors.background,
85+
),
86+
border = FilterChipDefaults.filterChipBorder(
87+
enabled = true,
88+
selected = selected,
89+
borderColor = colors.buttonOutline,
90+
selectedBorderColor = colors.accent,
91+
),
92+
)
93+
}
94+
95+
@Composable
96+
private fun languageFilterLabel(language: String, uiLocale: Locale): String {
97+
return remember(language, uiLocale) {
98+
val locale = Locale.forLanguageTag(language)
99+
val nativeName = locale.getDisplayLanguage(locale).ifBlank { language }
100+
val uiName = locale.getDisplayLanguage(uiLocale).ifBlank { language }
101+
if (nativeName.equals(uiName, ignoreCase = true)) {
102+
nativeName
103+
} else {
104+
"$nativeName ($uiName)"
105+
}
106+
}
107+
}

0 commit comments

Comments
 (0)