Skip to content

Commit

Permalink
Make explore tab functional
Browse files Browse the repository at this point in the history
Currently just displays trending within a given period

TODO: Offer the language and spoken language filtering options
TODO: Button to view awesome lists
  • Loading branch information
wingio committed Sep 3, 2024
1 parent 9a3048d commit 52dafdd
Show file tree
Hide file tree
Showing 16 changed files with 696 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
fragment TrendingRepository on Repository {
id
name
description
openGraphImageUrl
usesCustomOpenGraphImage
starsSince(period: $period)
contributorsCount
viewerHasStarred
stargazerCount
primaryLanguage {
color
name
}
owner {
__typename
avatarUrl
login
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
query Trending($period: TrendingPeriod!) {
trendingRepositories(period: $period, mobileSortOrder: true) {
...TrendingRepository
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.materiiapps.gloom.api.util.transform
import com.materiiapps.gloom.gql.type.IssueState
import com.materiiapps.gloom.gql.type.PullRequestState
import com.materiiapps.gloom.gql.type.ReactionContent
import com.materiiapps.gloom.gql.type.TrendingPeriod

class GraphQLRepository(
private val service: GraphQLService
Expand Down Expand Up @@ -70,6 +71,10 @@ class GraphQLRepository(

suspend fun getFeed(cursor: String? = null) = service.getFeed(cursor)

suspend fun getTrending(period: TrendingPeriod = TrendingPeriod.DAILY) = service.getTrending(period).transform {
it.trendingRepositories?.filterNotNull()?.map { it.trendingRepository } ?: emptyList()
}

suspend fun starRepo(id: String) = service.starRepo(id).transform {
(it.addStar?.starrable?.viewerHasStarred
?: false) to (it.addStar?.starrable?.stargazers?.totalCount ?: 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ import com.materiiapps.gloom.gql.RepoReleasesQuery
import com.materiiapps.gloom.gql.SponsoringQuery
import com.materiiapps.gloom.gql.StarRepoMutation
import com.materiiapps.gloom.gql.StarredReposQuery
import com.materiiapps.gloom.gql.TrendingQuery
import com.materiiapps.gloom.gql.UnfollowUserMutation
import com.materiiapps.gloom.gql.UnreactMutation
import com.materiiapps.gloom.gql.UnstarRepoMutation
import com.materiiapps.gloom.gql.UserProfileQuery
import com.materiiapps.gloom.gql.type.IssueState
import com.materiiapps.gloom.gql.type.PullRequestState
import com.materiiapps.gloom.gql.type.ReactionContent
import com.materiiapps.gloom.gql.type.TrendingPeriod
import io.ktor.http.HttpHeaders
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -188,6 +190,13 @@ class GraphQLService(
.response()
}

suspend fun getTrending(period: TrendingPeriod = TrendingPeriod.DAILY) = withContext(Dispatchers.IO) {
client
.query(TrendingQuery(period))
.addToken()
.response()
}

suspend fun starRepo(id: String) = withContext(Dispatchers.IO) {
client.mutation(StarRepoMutation(id))
.addToken()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@ class PreferenceManager(provider: SettingsProvider) :
var orgAvatarShape by enumPreference("org_avatar_shape", AvatarShape.RoundedCorner)
var orgAvatarRadius by intPreference("org_avatar_radius", 31)

var trendingPeriod by enumPreference("trending_period", Defaults.TRENDING_PERIOD)

init {
if (userAvatarRadius > 50) userAvatarRadius = 50
if (userAvatarRadius < 0) userAvatarRadius = 0
if (orgAvatarRadius > 50) orgAvatarRadius = 50
if (orgAvatarRadius < 0) orgAvatarRadius = 0
}

companion object Defaults {

val TRENDING_PERIOD = TrendingPeriodPreference.DAILY

}

}

enum class Theme {
Expand All @@ -38,4 +46,10 @@ enum class AvatarShape {
Circle,
RoundedCorner,
Squircle
}

enum class TrendingPeriodPreference {
DAILY,
WEEKLY,
MONTHLY
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.materiiapps.gloom.ui.util

import android.icu.number.Notation
import android.icu.text.CompactDecimalFormat
import android.os.Build
import java.text.DecimalFormat
import java.util.Locale
import android.icu.number.NumberFormatter as AndroidNumberFormatter

actual object NumberFormatter {

actual fun compact(count: Int): String {
return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
AndroidNumberFormatter
.withLocale(Locale.getDefault())
.notation(Notation.compactShort())
.format(count)
.toString()
}

Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> {
CompactDecimalFormat
.getInstance(
Locale.getDefault(),
CompactDecimalFormat.CompactStyle.SHORT
)
.format(count)
}

else -> DecimalFormat().format(count)
}
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.materiiapps.gloom.di.module

import com.materiiapps.gloom.ui.screen.auth.viewmodel.LandingViewModel
import com.materiiapps.gloom.ui.screen.explore.viewmodel.ExploreViewModel
import com.materiiapps.gloom.ui.screen.explorer.viewmodel.DirectoryListingViewModel
import com.materiiapps.gloom.ui.screen.explorer.viewmodel.FileViewerViewModel
import com.materiiapps.gloom.ui.screen.home.viewmodel.HomeViewModel
Expand All @@ -12,12 +13,12 @@ import com.materiiapps.gloom.ui.screen.profile.viewmodel.FollowersViewModel
import com.materiiapps.gloom.ui.screen.profile.viewmodel.FollowingViewModel
import com.materiiapps.gloom.ui.screen.profile.viewmodel.ProfileViewModel
import com.materiiapps.gloom.ui.screen.repo.viewmodel.LicenseViewModel
import com.materiiapps.gloom.ui.screen.repo.viewmodel.RepoViewModel
import com.materiiapps.gloom.ui.screen.repo.viewmodel.RepoCodeViewModel
import com.materiiapps.gloom.ui.screen.repo.viewmodel.RepoDetailsViewModel
import com.materiiapps.gloom.ui.screen.repo.viewmodel.RepoIssuesViewModel
import com.materiiapps.gloom.ui.screen.repo.viewmodel.RepoPullRequestsViewModel
import com.materiiapps.gloom.ui.screen.repo.viewmodel.RepoReleasesViewModel
import com.materiiapps.gloom.ui.screen.repo.viewmodel.RepoViewModel
import com.materiiapps.gloom.ui.screen.settings.viewmodel.AccountSettingsViewModel
import com.materiiapps.gloom.ui.screen.settings.viewmodel.AppearanceSettingsViewModel
import com.materiiapps.gloom.ui.screen.settings.viewmodel.SettingsViewModel
Expand All @@ -38,6 +39,7 @@ fun viewModelModule() = module {
factoryOf(::AppearanceSettingsViewModel)
factoryOf(::AccountSettingsViewModel)
factoryOf(::HomeViewModel)
factoryOf(::ExploreViewModel)

factoryOf(::RepoViewModel)
factoryOf(::RepoDetailsViewModel)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.materiiapps.gloom.ui.component.filter

import androidx.compose.foundation.layout.Box
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.InputChip
import androidx.compose.material3.Text
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.unit.DpOffset
import androidx.compose.ui.unit.dp
import kotlin.enums.enumEntries

/**
* An [InputChip] used to make a single selection from a set
* of options using an enum.
*
* Clicking the chip will open a dropdown containing all the entries
* contained in the provided enum [E], once the user selects an item [onChoiceSelected]
* will be called and the dropdown dismissed.
*
* @param E The enum used to supply the choices
*
* @param defaultValue The default value, if the current choice differs from this value then
* the chip will be marked as selected
* @param currentValue The currently chosen item
* @param onChoiceSelected Called when the user makes their choice
* @param modifier The [Modifier] for this chip
* @param label Factory function used to get a localized label for the enum entry, defaults to the enum entries name.
* It is not reccommended to override the text style or add anything more than a [Text] component.
*/
@Composable
inline fun <reified E: Enum<E>> ChoiceInputChip(
defaultValue: E,
currentValue: E,
crossinline onChoiceSelected: (E) -> Unit,
modifier: Modifier = Modifier,
crossinline label: @Composable (E) -> Unit = { Text(it.name) }
) {
var dropdownVisible by remember { mutableStateOf(false) }

InputChip(
onClick = { dropdownVisible = true },
selected = currentValue != defaultValue,
label = { label(currentValue) },
trailingIcon = {
Icon(
imageVector = Icons.Outlined.ExpandMore,
contentDescription = null
)
},
modifier = modifier
)

Box {
DropdownMenu(
expanded = dropdownVisible,
onDismissRequest = { dropdownVisible = false },
offset = DpOffset(0.dp, 24.dp)
) {
enumEntries<E>().forEach { entry ->
DropdownMenuItem(
text = { label(entry) },
onClick = {
dropdownVisible = false
if (currentValue != entry) onChoiceSelected(entry)
}
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
package com.materiiapps.gloom.ui.screen.explore

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Explore
import androidx.compose.material.icons.outlined.Explore
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.input.nestedscroll.nestedScroll
import cafe.adriel.voyager.koin.getScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
import cafe.adriel.voyager.navigator.tab.Tab
import cafe.adriel.voyager.navigator.tab.TabOptions
import com.materiiapps.gloom.Res
import com.materiiapps.gloom.ui.screen.explore.component.TrendingFeedFooter
import com.materiiapps.gloom.ui.screen.explore.component.TrendingFeedHeader
import com.materiiapps.gloom.ui.screen.explore.component.TrendingRepoItem
import com.materiiapps.gloom.ui.screen.explore.viewmodel.ExploreViewModel
import com.materiiapps.gloom.ui.screen.profile.ProfileScreen
import com.materiiapps.gloom.ui.screen.repo.RepoScreen
import com.materiiapps.gloom.ui.util.navigate
import dev.icerock.moko.resources.compose.stringResource

class ExploreScreen : Tab {

override val options: TabOptions
@Composable get() {
val navigator = LocalTabNavigator.current
Expand All @@ -31,21 +51,69 @@ class ExploreScreen : Tab {
override fun Content() = Screen()

@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun Screen() {
val viewModel: ExploreViewModel = getScreenModel()
val navigator = LocalNavigator.currentOrThrow
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()

Scaffold(
topBar = { TopBar() }
) {
topBar = { TopBar(scrollBehavior) }
) { pv ->
PullToRefreshBox(
isRefreshing = viewModel.isLoading,
onRefresh = { viewModel.loadTrending() },
modifier = Modifier
.padding(pv)
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection)
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
item("header") {
TrendingFeedHeader(
currentTrendingPeriod = viewModel.trendingPeriod,
onTrendingPeriodChange = { newPeriod -> viewModel.updateTrendingPeriod(newPeriod) }
)
}

items(
viewModel.trendingRepos,
key = { it.id }
) { trendingRepo ->
TrendingRepoItem(
trendingRepository = trendingRepo,
trendingPeriod = viewModel.trendingPeriod,
starToggleEnabled = !viewModel.repoStarsPending.contains(trendingRepo.id),
onClick = { navigator.navigate(RepoScreen(trendingRepo.owner.login, trendingRepo.name)) },
onOwnerClick = { navigator.navigate(ProfileScreen(trendingRepo.owner.login)) },
onStarClick = { viewModel.starRepo(trendingRepo.id) },
onUnstarClick = { viewModel.unstarRepo(trendingRepo.id) }
)
}

if (viewModel.trendingRepos.isNotEmpty()) {
item("footer") {
TrendingFeedFooter()
}
}
}
}
}
}

@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun TopBar() {
private fun TopBar(
scrollBehavior: TopAppBarScrollBehavior
) {
LargeTopAppBar(
title = {
Text(text = options.title)
}
},
scrollBehavior = scrollBehavior
)
}

}
Loading

0 comments on commit 52dafdd

Please sign in to comment.