Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -103,6 +103,7 @@ fun App(deepLinkUri: String? = null) {
AppNavigation(
navController = navController,
isLiquidGlassEnabled = state.isLiquidGlassEnabled,
isScrollbarEnabled = state.isScrollbarEnabled,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ data class MainState(
val isDarkTheme: Boolean? = null,
val currentFontTheme: FontTheme = FontTheme.CUSTOM,
val isLiquidGlassEnabled: Boolean = true,
val isScrollbarEnabled: Boolean = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ class MainViewModel(
}
}

viewModelScope.launch {
tweaksRepository.getScrollbarEnabled().collect { enabled ->
_state.update { it.copy(isScrollbarEnabled = enabled) }
}
}

viewModelScope.launch {
rateLimitRepository.rateLimitState.collect { rateLimitInfo ->
_state.update { currentState ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import zed.rainxch.apps.presentation.AppsViewModel
import zed.rainxch.auth.presentation.AuthenticationRoot
import zed.rainxch.core.presentation.locals.LocalBottomNavigationHeight
import zed.rainxch.core.presentation.locals.LocalBottomNavigationLiquid
import zed.rainxch.core.presentation.locals.LocalScrollbarEnabled
import zed.rainxch.details.presentation.DetailsRoot
import zed.rainxch.devprofile.presentation.DeveloperProfileRoot
import zed.rainxch.favourites.presentation.FavouritesRoot
Expand All @@ -43,6 +44,7 @@ import zed.rainxch.starred.presentation.StarredReposRoot
fun AppNavigation(
navController: NavHostController,
isLiquidGlassEnabled: Boolean = true,
isScrollbarEnabled: Boolean = false,
) {
val liquidState = rememberLiquidState()
var bottomNavigationHeight by remember { mutableStateOf(0.dp) }
Expand All @@ -54,6 +56,7 @@ fun AppNavigation(
CompositionLocalProvider(
LocalBottomNavigationLiquid provides liquidState,
LocalBottomNavigationHeight provides bottomNavigationHeight,
LocalScrollbarEnabled provides isScrollbarEnabled,
) {
Box(
modifier = Modifier.fillMaxSize(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@ class TweaksRepositoryImpl(
}
}

override fun getScrollbarEnabled(): Flow<Boolean> =
preferences.data.map { prefs ->
prefs[SCROLLBAR_ENABLED_KEY] ?: false
}

override suspend fun setScrollbarEnabled(enabled: Boolean) {
preferences.edit { prefs ->
prefs[SCROLLBAR_ENABLED_KEY] = enabled
}
}

companion object {
private const val DEFAULT_UPDATE_CHECK_INTERVAL_HOURS = 6L

Expand All @@ -172,5 +183,6 @@ class TweaksRepositoryImpl(
private val INCLUDE_PRE_RELEASES_KEY = booleanPreferencesKey("include_pre_releases")
private val LIQUID_GLASS_ENABLED_KEY = booleanPreferencesKey("liquid_glass_enabled")
private val HIDE_SEEN_ENABLED_KEY = booleanPreferencesKey("hide_seen_enabled")
private val SCROLLBAR_ENABLED_KEY = booleanPreferencesKey("scrollbar_enabled")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ interface TweaksRepository {
fun getDiscoveryPlatform(): Flow<DiscoveryPlatform>

suspend fun setDiscoveryPlatform(platform: DiscoveryPlatform)

fun getScrollbarEnabled(): Flow<Boolean>

suspend fun setScrollbarEnabled(enabled: Boolean)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package zed.rainxch.core.presentation.components

import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

@Composable
actual fun ScrollbarContainer(
listState: LazyListState,
enabled: Boolean,
modifier: Modifier,
content: @Composable () -> Unit,
) {
content()
}

@Composable
actual fun ScrollbarContainer(
gridState: LazyStaggeredGridState,
enabled: Boolean,
modifier: Modifier,
content: @Composable () -> Unit,
) {
content()
Comment thread
rainxchzed marked this conversation as resolved.
Outdated
}
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@
<string name="auth_hint_denied">لقد رفضت طلب التفويض. حاول مرة أخرى إذا كان ذلك غير مقصود.</string>
<string name="auth_check_status">لقد قمت بالتفويض بالفعل</string>
<string name="auth_polling_status">جارٍ التحقق…</string>
<string name="auth_rate_limited">تم تقييد المعدل — إعادة المحاولة خلال %1$d ثانية</string>

<!-- Read More / Show Less -->
<string name="read_more">اقرأ المزيد</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@
<string name="auth_hint_denied">আপনি অনুমোদনের অনুরোধ প্রত্যাখ্যান করেছেন। এটি অনিচ্ছাকৃত হলে আবার চেষ্টা করুন।</string>
<string name="auth_check_status">আমি ইতিমধ্যেই অনুমোদন করেছি</string>
<string name="auth_polling_status">যাচাই করা হচ্ছে…</string>
<string name="auth_rate_limited">অনুরোধ সীমাবদ্ধ — %1$d সেকেন্ডে পুনরায় চেষ্টা করা হবে</string>

<!-- Read More / Show Less -->
<string name="read_more">আরও পড়ুন</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@
<string name="auth_hint_denied">Rechazaste la solicitud de autorización. Intenta de nuevo si fue involuntario.</string>
<string name="auth_check_status">Ya he autorizado</string>
<string name="auth_polling_status">Comprobando…</string>
<string name="auth_rate_limited">Límite alcanzado — reintentando en %1$d s</string>

<!-- Read More / Show Less -->
<string name="read_more">Leer más</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@
<string name="auth_hint_denied">Vous avez refusé la demande d\'autorisation. Réessayez si c\'était involontaire.</string>
<string name="auth_check_status">J’ai déjà autorisé</string>
<string name="auth_polling_status">Vérification…</string>
<string name="auth_rate_limited">Limite atteinte — nouvelle tentative dans %1$d s</string>

<!-- Read More / Show Less -->
<string name="read_more">Lire la suite</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@
<string name="auth_hint_denied">आपने प्राधिकरण अनुरोध अस्वीकार कर दिया। यदि यह अनजाने में हुआ तो पुनः प्रयास करें।</string>
<string name="auth_check_status">मैं पहले ही अनुमति दे चुका हूँ</string>
<string name="auth_polling_status">जाँच की जा रही है…</string>
<string name="auth_rate_limited">दर सीमा पार — %1$d सेकंड में पुनः प्रयास</string>

<!-- Read More / Show Less -->
<string name="read_more">और पढ़ें</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@
<string name="auth_hint_denied">Hai rifiutato la richiesta di autorizzazione. Riprova se è stato involontario.</string>
<string name="auth_check_status">Ho già autorizzato</string>
<string name="auth_polling_status">Verifica in corso…</string>
<string name="auth_rate_limited">Limite raggiunto — nuovo tentativo tra %1$d s</string>

<!-- Read More / Show Less -->
<string name="read_more">Leggi di più</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@
<string name="auth_hint_denied">認証リクエストを拒否しました。意図しない場合は再試行してください。</string>
<string name="auth_check_status">すでに認証しました</string>
<string name="auth_polling_status">確認中…</string>
<string name="auth_rate_limited">レート制限に達しました — %1$d秒後に再試行</string>

<!-- Read More / Show Less -->
<string name="read_more">もっと読む</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@
<string name="auth_hint_denied">인증 요청을 거부했습니다. 의도하지 않은 경우 다시 시도하세요.</string>
<string name="auth_check_status">이미 인증했습니다</string>
<string name="auth_polling_status">확인 중…</string>
<string name="auth_rate_limited">요청 제한 초과 — %1$d초 후 다시 시도</string>

<!-- Read More / Show Less -->
<string name="read_more">더 보기</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@
<string name="auth_hint_denied">Odrzuciłeś żądanie autoryzacji. Spróbuj ponownie, jeśli było to niezamierzone.</string>
<string name="auth_check_status">Już autoryzowałem</string>
<string name="auth_polling_status">Sprawdzanie…</string>
<string name="auth_rate_limited">Osiągnięto limit — ponowna próba za %1$d s</string>

<!-- Read More / Show Less -->
<string name="read_more">Czytaj więcej</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@
<string name="auth_hint_denied">Вы отклонили запрос авторизации. Попробуйте снова, если это было непреднамеренно.</string>
<string name="auth_check_status">Я уже авторизовался</string>
<string name="auth_polling_status">Проверка…</string>
<string name="auth_rate_limited">Превышен лимит — повтор через %1$d с</string>

<!-- Read More / Show Less -->
<string name="read_more">Читать далее</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@
<string name="auth_hint_denied">Yetkilendirme isteğini reddettiniz. İstemeden yaptıysanız tekrar deneyin.</string>
<string name="auth_check_status">Zaten yetkilendirdim</string>
<string name="auth_polling_status">Kontrol ediliyor…</string>
<string name="auth_rate_limited">İstek sınırına ulaşıldı — %1$d sn sonra tekrar denenecek</string>

<!-- Read More / Show Less -->
<string name="read_more">Devamını oku</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@
<string name="auth_hint_denied">您拒绝了授权请求。如果是误操作,请重试。</string>
<string name="auth_check_status">我已经授权</string>
<string name="auth_polling_status">正在检查…</string>
<string name="auth_rate_limited">请求过于频繁 — 将在 %1$d 秒后重试</string>

<!-- Read More / Show Less -->
<string name="read_more">阅读更多</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,9 @@
<string name="liquid_glass_option_title">Liquid Glass Effect</string>
<string name="liquid_glass_option_description">Enhance the interface with a smooth glass-like appearance</string>

<string name="scrollbar_option_title">Scrollbar</string>
<string name="scrollbar_option_description">Show scrollbar on scrollable lists (desktop)</string>

<string name="hide_seen_title">Hide Seen Repositories</string>
<string name="hide_seen_description">Hide repositories you have already viewed from discovery feeds</string>
<string name="clear_seen_history">Clear Seen History</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package zed.rainxch.core.presentation.components

import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

/**
* Wraps content with a platform-appropriate scrollbar.
* On Desktop (JVM), adds a VerticalScrollbar when [enabled] is true.
* On Android, renders only the [content] (no scrollbar).
*/
@Composable
expect fun ScrollbarContainer(
listState: LazyListState,
enabled: Boolean,
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
)

/**
* Overload for [LazyStaggeredGridState].
*/
@Composable
expect fun ScrollbarContainer(
gridState: LazyStaggeredGridState,
enabled: Boolean,
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package zed.rainxch.core.presentation.locals

import androidx.compose.runtime.compositionLocalOf

/**
* CompositionLocal providing whether the scrollbar should be shown.
* Defaults to false (no scrollbar).
*/
val LocalScrollbarEnabled = compositionLocalOf { false }
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package zed.rainxch.core.presentation.components

import androidx.compose.foundation.LocalScrollbarStyle
import androidx.compose.foundation.ScrollbarAdapter
import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
actual fun ScrollbarContainer(
listState: LazyListState,
enabled: Boolean,
modifier: Modifier,
content: @Composable () -> Unit,
) {
if (!enabled) {
content()
return
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Box(modifier = modifier.padding(start = 8.dp)) {
val scrollbarStyle =
LocalScrollbarStyle.current.copy(
shape = RoundedCornerShape(32.dp),
unhoverColor = MaterialTheme.colorScheme.onSurface,
hoverColor = MaterialTheme.colorScheme.onSurfaceVariant,
)
content()
VerticalScrollbar(
adapter = rememberScrollbarAdapter(listState),
modifier =
Modifier
.fillMaxHeight()
.align(Alignment.CenterEnd),
style = scrollbarStyle,
)
}
}

@Composable
actual fun ScrollbarContainer(
gridState: LazyStaggeredGridState,
enabled: Boolean,
modifier: Modifier,
content: @Composable () -> Unit,
) {
if (!enabled) {
content()
return
}
Box(modifier = modifier.padding(start = 8.dp)) {
val scrollbarStyle =
LocalScrollbarStyle.current.copy(
shape = RoundedCornerShape(32.dp),
unhoverColor = MaterialTheme.colorScheme.onSurface,
hoverColor = MaterialTheme.colorScheme.onSurfaceVariant,
)

content()
val adapter = remember(gridState) { StaggeredGridScrollbarAdapter(gridState) }
VerticalScrollbar(
adapter = adapter,
modifier =
Modifier
.fillMaxHeight()
.align(Alignment.CenterEnd),
style = scrollbarStyle,
)
}
}

/**
* Custom [ScrollbarAdapter] for [LazyStaggeredGridState] since Compose Desktop
* does not provide a built-in [rememberScrollbarAdapter] overload for staggered grids.
*/
private class StaggeredGridScrollbarAdapter(
private val gridState: LazyStaggeredGridState,
) : ScrollbarAdapter {
override val scrollOffset: Float
get() {
val layoutInfo = gridState.layoutInfo
val firstVisible = layoutInfo.visibleItemsInfo.firstOrNull() ?: return 0f
val fraction = firstVisible.index.toFloat() / maxOf(layoutInfo.totalItemsCount, 1)
return fraction * estimatedContentSize() - firstVisible.offset.y.toFloat()
}

override fun maxScrollOffset(containerSize: Int): Float = (estimatedContentSize() - containerSize).coerceAtLeast(0f)

override suspend fun scrollTo(
containerSize: Int,
scrollOffset: Float,
) {
val totalContent = estimatedContentSize()
val layoutInfo = gridState.layoutInfo
if (layoutInfo.totalItemsCount == 0 || totalContent <= 0f) return
val fraction = scrollOffset / totalContent
val targetIndex =
(fraction * layoutInfo.totalItemsCount)
.toInt()
.coerceIn(0, maxOf(layoutInfo.totalItemsCount - 1, 0))
gridState.scrollToItem(targetIndex)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

private fun estimatedContentSize(): Float {
val layoutInfo = gridState.layoutInfo
if (layoutInfo.totalItemsCount == 0) return 0f
val visibleItems = layoutInfo.visibleItemsInfo
if (visibleItems.isEmpty()) return 0f
val avgHeight = visibleItems.map { it.size.height }.average().toFloat()
val laneCount =
maxOf(
visibleItems.maxOf { it.lane + 1 },
1,
)
val rows = (layoutInfo.totalItemsCount + laneCount - 1) / laneCount
return rows * avgHeight + layoutInfo.beforeContentPadding + layoutInfo.afterContentPadding
}
}
Loading