feat: zombie mode (#107)

This commit is contained in:
Diego Beraldin 2023-11-06 21:58:08 +01:00 committed by GitHub
parent 76d582f014
commit eb990ccd82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 777 additions and 301 deletions

View File

@ -2,6 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy.core.commonui.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.core.tween
@ -64,7 +65,10 @@ fun FloatingActionButtonMenu(
}
val numberOfItems by animateIntAsState(
targetValue = if (fabExpanded) items.size else 0,
animationSpec = tween(250 * items.size)
animationSpec = tween(
durationMillis = 150 * items.size,
easing = LinearEasing,
)
)
val indices: List<Int> = if (numberOfItems == 0) {
emptyList()

View File

@ -43,6 +43,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.BottomS
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCardBody
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.ScaledContent
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getCommunityInfoViewModel
import com.github.diegoberaldin.raccoonforlemmy.core.utils.getPrettyNumber
import com.github.diegoberaldin.raccoonforlemmy.core.utils.prettifyDate
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
@ -115,37 +116,55 @@ class CommunityInfoScreen(
modifier = Modifier.fillMaxWidth(),
icon = Icons.Default.Padding,
title = stringResource(MR.strings.community_info_posts),
value = uiState.community.posts.toString(),
value = uiState.community.posts.getPrettyNumber(
thousandLabel = stringResource(MR.strings.profile_thousand_short),
millionLabel = stringResource(MR.strings.profile_million_short),
),
)
CommunityInfoItem(
modifier = Modifier.fillMaxWidth(),
icon = Icons.Default.Reply,
title = stringResource(MR.strings.community_info_comments),
value = uiState.community.comments.toString(),
value = uiState.community.comments.getPrettyNumber(
thousandLabel = stringResource(MR.strings.profile_thousand_short),
millionLabel = stringResource(MR.strings.profile_million_short),
),
)
CommunityInfoItem(
modifier = Modifier.fillMaxWidth(),
icon = Icons.Default.Group,
title = stringResource(MR.strings.community_info_subscribers),
value = uiState.community.subscribers.toString(),
value = uiState.community.subscribers.getPrettyNumber(
thousandLabel = stringResource(MR.strings.profile_thousand_short),
millionLabel = stringResource(MR.strings.profile_million_short),
),
)
CommunityInfoItem(
modifier = Modifier.fillMaxWidth(),
icon = Icons.Default.CalendarViewMonth,
title = stringResource(MR.strings.community_info_monthly_active_users),
value = uiState.community.monthlyActiveUsers.toString(),
value = uiState.community.monthlyActiveUsers.getPrettyNumber(
thousandLabel = stringResource(MR.strings.profile_thousand_short),
millionLabel = stringResource(MR.strings.profile_million_short),
),
)
CommunityInfoItem(
modifier = Modifier.fillMaxWidth(),
icon = Icons.Default.CalendarViewWeek,
title = stringResource(MR.strings.community_info_weekly_active_users),
value = uiState.community.weeklyActiveUsers.toString(),
value = uiState.community.weeklyActiveUsers.getPrettyNumber(
thousandLabel = stringResource(MR.strings.profile_thousand_short),
millionLabel = stringResource(MR.strings.profile_million_short),
),
)
CommunityInfoItem(
modifier = Modifier.fillMaxWidth(),
icon = Icons.Default.CalendarViewDay,
title = stringResource(MR.strings.community_info_daily_active_users),
value = uiState.community.dailyActiveUsers.toString(),
value = uiState.community.dailyActiveUsers.getPrettyNumber(
thousandLabel = stringResource(MR.strings.profile_thousand_short),
millionLabel = stringResource(MR.strings.profile_million_short),
),
)
Divider()

View File

@ -30,6 +30,8 @@ interface CommunityDetailMviModel :
data object Block : Intent
data object BlockInstance : Intent
data object ClearRead : Intent
data class StartZombieMode(val index: Int) : Intent
data object PauseZombieMode : Intent
}
data class UiState(
@ -48,11 +50,13 @@ interface CommunityDetailMviModel :
val fullHeightImages: Boolean = true,
val separateUpAndDownVotes: Boolean = false,
val autoLoadImages: Boolean = true,
val zombieModeActive: Boolean = false,
)
sealed interface Effect {
data object BlockSuccess : Effect
data class BlockError(val message: String?) : Effect
data object BackToTop : Effect
data class ZombieModeTick(val index: Int) : Effect
}
}

View File

@ -6,6 +6,7 @@ import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@ -24,6 +25,8 @@ import androidx.compose.material.icons.filled.ArrowCircleUp
import androidx.compose.material.icons.filled.ClearAll
import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.Sync
import androidx.compose.material.icons.filled.SyncDisabled
import androidx.compose.material.icons.outlined.AddCircleOutline
import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material.icons.outlined.Pending
@ -181,10 +184,10 @@ class CommunityDetailScreen(
)
}
LaunchedEffect(model) {
model.effects.onEach {
when (it) {
model.effects.onEach { effect ->
when (effect) {
is CommunityDetailMviModel.Effect.BlockError -> {
snackbarHostState.showSnackbar(it.message ?: genericError)
snackbarHostState.showSnackbar(effect.message ?: genericError)
}
CommunityDetailMviModel.Effect.BlockSuccess -> {
@ -192,8 +195,12 @@ class CommunityDetailScreen(
}
CommunityDetailMviModel.Effect.BackToTop -> {
scope.launch {
lazyListState.scrollToItem(0)
lazyListState.scrollToItem(0)
}
is CommunityDetailMviModel.Effect.ZombieModeTick -> {
if (effect.index >= 0) {
lazyListState.scrollToItem(effect.index)
}
}
}
@ -283,6 +290,28 @@ class CommunityDetailScreen(
) {
FloatingActionButtonMenu(
items = buildList {
if (uiState.zombieModeActive) {
this += FloatingActionButtonMenuItem(
icon = Icons.Default.SyncDisabled,
text = stringResource(MR.strings.action_deactivate_zombie_mode),
onSelected = rememberCallback(model) {
model.reduce(CommunityDetailMviModel.Intent.PauseZombieMode)
},
)
} else {
this += FloatingActionButtonMenuItem(
icon = Icons.Default.Sync,
text = stringResource(MR.strings.action_activate_zombie_mode),
onSelected = rememberCallback(model) {
val idx = lazyListState.firstVisibleItemIndex
model.reduce(
CommunityDetailMviModel.Intent.StartZombieMode(
idx + 1// header
)
)
},
)
}
this += FloatingActionButtonMenuItem(
icon = Icons.Default.ExpandLess,
text = stringResource(MR.strings.action_back_to_top),
@ -343,45 +372,46 @@ class CommunityDetailScreen(
) {
LazyColumn(
state = lazyListState,
userScrollEnabled = !uiState.zombieModeActive,
) {
item {
CommunityHeader(
community = uiState.community,
autoLoadImages = uiState.autoLoadImages,
options = listOf(
stringResource(MR.strings.community_detail_info),
stringResource(MR.strings.community_detail_instance_info),
stringResource(MR.strings.community_detail_block),
stringResource(MR.strings.community_detail_block_instance),
),
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.getRootNavigator()
?.push(ZoomableImageScreen(url))
},
onOptionSelected = rememberCallbackArgs { optionIdx ->
when (optionIdx) {
3 -> model.reduce(CommunityDetailMviModel.Intent.BlockInstance)
2 -> model.reduce(CommunityDetailMviModel.Intent.Block)
Column {
CommunityHeader(
community = uiState.community,
autoLoadImages = uiState.autoLoadImages,
options = listOf(
stringResource(MR.strings.community_detail_info),
stringResource(MR.strings.community_detail_instance_info),
stringResource(MR.strings.community_detail_block),
stringResource(MR.strings.community_detail_block_instance),
),
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.getRootNavigator()
?.push(ZoomableImageScreen(url))
},
onOptionSelected = rememberCallbackArgs { optionIdx ->
when (optionIdx) {
3 -> model.reduce(CommunityDetailMviModel.Intent.BlockInstance)
2 -> model.reduce(CommunityDetailMviModel.Intent.Block)
1 -> {
navigationCoordinator.getRootNavigator()?.push(
InstanceInfoScreen(
url = uiState.community.instanceUrl,
),
)
}
1 -> {
navigationCoordinator.getRootNavigator()?.push(
InstanceInfoScreen(
url = uiState.community.instanceUrl,
),
)
}
else -> {
navigationCoordinator.getBottomNavigator()?.show(
CommunityInfoScreen(uiState.community),
)
else -> {
navigationCoordinator.getBottomNavigator()?.show(
CommunityInfoScreen(uiState.community),
)
}
}
}
},
)
}
item {
Spacer(modifier = Modifier.height(Spacing.m))
},
)
Spacer(modifier = Modifier.height(Spacing.m))
}
}
if (uiState.posts.isEmpty() && uiState.loading) {
items(5) {

View File

@ -6,6 +6,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.utils.HapticFeedback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.ShareHelper
import com.github.diegoberaldin.raccoonforlemmy.core.utils.ZombieModeHelper
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
@ -33,6 +34,7 @@ class CommunityDetailViewModel(
private val settingsRepository: SettingsRepository,
private val shareHelper: ShareHelper,
private val hapticFeedback: HapticFeedback,
private val zombieModeHelper: ZombieModeHelper,
) : CommunityDetailMviModel,
MviModel<CommunityDetailMviModel.Intent, CommunityDetailMviModel.UiState, CommunityDetailMviModel.Effect> by mvi {
@ -69,6 +71,10 @@ class CommunityDetailViewModel(
}
}.launchIn(this)
zombieModeHelper.index.onEach { index ->
mvi.emitEffect(CommunityDetailMviModel.Effect.ZombieModeTick(index))
}.launchIn(this)
if (uiState.value.currentUserId == null) {
val user = siteRepository.getCurrentUser(auth)
mvi.updateState { it.copy(currentUserId = user?.id ?: 0) }
@ -136,6 +142,16 @@ class CommunityDetailViewModel(
hide(post = post)
}
}
CommunityDetailMviModel.Intent.PauseZombieMode -> {
mvi.updateState { it.copy(zombieModeActive = false) }
zombieModeHelper.pause()
}
is CommunityDetailMviModel.Intent.StartZombieMode -> {
mvi.updateState { it.copy(zombieModeActive = true) }
zombieModeHelper.start(intent.index)
}
}
}

View File

@ -37,10 +37,13 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.utils.getPrettyNumber
import com.github.diegoberaldin.raccoonforlemmy.core.utils.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalDp
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import dev.icerock.moko.resources.compose.stringResource
@Composable
fun CommunityHeader(
@ -201,7 +204,10 @@ fun CommunityHeader(
contentDescription = null
)
Text(
text = community.subscribers.toString(),
text = community.subscribers.getPrettyNumber(
thousandLabel = stringResource(MR.strings.profile_thousand_short),
millionLabel = stringResource(MR.strings.profile_million_short),
),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onBackground,
)
@ -213,7 +219,10 @@ fun CommunityHeader(
contentDescription = null
)
Text(
text = community.monthlyActiveUsers.toString(),
text = community.monthlyActiveUsers.getPrettyNumber(
thousandLabel = stringResource(MR.strings.profile_thousand_short),
millionLabel = stringResource(MR.strings.profile_million_short),
),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onBackground,
)

View File

@ -40,11 +40,14 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.utils.getPrettyNumber
import com.github.diegoberaldin.raccoonforlemmy.core.utils.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.prettifyDate
import com.github.diegoberaldin.raccoonforlemmy.core.utils.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalDp
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import dev.icerock.moko.resources.compose.stringResource
@Composable
fun UserHeader(
@ -203,7 +206,10 @@ fun UserHeader(
contentDescription = null
)
Text(
text = postScore.toString(),
text = postScore.getPrettyNumber(
thousandLabel = stringResource(MR.strings.profile_thousand_short),
millionLabel = stringResource(MR.strings.profile_million_short),
),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onBackground,
)
@ -218,7 +224,10 @@ fun UserHeader(
contentDescription = null
)
Text(
text = commentScore.toString(),
text = commentScore.getPrettyNumber(
thousandLabel = stringResource(MR.strings.profile_thousand_short),
millionLabel = stringResource(MR.strings.profile_million_short),
),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onBackground,
)

View File

@ -31,9 +31,14 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.saveditems.SavedIt
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.saveditems.SavedItemsViewModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailViewModel
import com.github.diegoberaldin.raccoonforlemmy.core.utils.di.utilsModule
import org.koin.dsl.module
val commonUiModule = module {
includes(
utilsModule,
)
single<NavigationCoordinator> {
DefaultNavigationCoordinator()
}
@ -73,6 +78,7 @@ val commonUiModule = module {
settingsRepository = get(),
shareHelper = get(),
hapticFeedback = get(),
zombieModeHelper = get(),
)
}
factory<CommunityInfoMviModel> { params ->

View File

@ -0,0 +1,103 @@
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import cafe.adriel.voyager.core.screen.Screen
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.BottomSheetHandle
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.utils.getPrettyDuration
import com.github.diegoberaldin.raccoonforlemmy.core.utils.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import dev.icerock.moko.resources.compose.stringResource
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
class DurationBottomSheet(
private val values: List<Duration> = listOf(
1.seconds,
2.seconds,
2.5.seconds,
3.seconds,
3.5.seconds,
4.seconds,
4.5.seconds,
5.seconds,
),
) : Screen {
@Composable
override fun Content() {
val navigationCoordinator = remember { getNavigationCoordinator() }
val notificationCenter = remember { getNotificationCenter() }
Column(
modifier = Modifier.padding(
top = Spacing.s,
start = Spacing.s,
end = Spacing.s,
bottom = Spacing.m,
),
verticalArrangement = Arrangement.spacedBy(Spacing.s),
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
BottomSheetHandle()
Text(
modifier = Modifier.padding(start = Spacing.s, top = Spacing.s),
text = stringResource(MR.strings.settings_zombie_mode_interval),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onBackground,
)
}
Column(
modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(Spacing.xxxs),
) {
for (value in values) {
Row(
modifier = Modifier.padding(
horizontal = Spacing.s,
vertical = Spacing.s,
).fillMaxWidth().onClick(
rememberCallback {
notificationCenter.getObserver(
NotificationCenterContractKeys.ChangeZombieInterval
)?.also {
it.invoke(value)
}
navigationCoordinator.getBottomNavigator()?.hide()
},
),
) {
Text(
text = value.getPrettyDuration(
secondsLabel = stringResource(MR.strings.post_second_short),
minutesLabel = stringResource(MR.strings.post_minute_short),
hoursLabel = stringResource(MR.strings.post_hour_short),
),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onBackground,
)
}
}
}
}
}
}

View File

@ -39,6 +39,7 @@ import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import dev.icerock.moko.resources.compose.stringResource
class SortBottomSheet(
private val contract: String = NotificationCenterContractKeys.ChangeSortType,
private val values: List<SortType> = listOf(
SortType.Active,
SortType.Hot,
@ -70,6 +71,7 @@ class SortBottomSheet(
SortBottomSheetMain(
values = values,
expandTop = expandTop,
contract = contract,
)
)
}
@ -77,6 +79,7 @@ class SortBottomSheet(
}
internal class SortBottomSheetMain(
private val contract: String,
private val values: List<SortType>,
private val expandTop: Boolean = false,
) : Screen {
@ -108,13 +111,9 @@ internal class SortBottomSheetMain(
.onClick(
rememberCallback {
if (value == SortType.Top.Generic && expandTop) {
navigator.push(
SortBottomSheetTop()
)
navigator.push(SortBottomSheetTop(contract = contract))
} else {
notificationCenter.getAllObservers(
NotificationCenterContractKeys.ChangeSortType
).forEach {
notificationCenter.getAllObservers(contract).forEach {
it.invoke(value)
}
navigationCoordinator.getBottomNavigator()?.hide()
@ -149,6 +148,7 @@ internal class SortBottomSheetMain(
}
internal class SortBottomSheetTop(
private val contract: String,
private val values: List<SortType> = listOf(
SortType.Top.PastHour,
SortType.Top.Past6Hours,
@ -201,11 +201,10 @@ internal class SortBottomSheetTop(
.onClick(
rememberCallback {
notificationCenter.getAllObservers(
NotificationCenterContractKeys.ChangeSortType
)
.forEach {
it.invoke(value)
}
contract,
).forEach {
it.invoke(value)
}
navigationCoordinator.getBottomNavigator()?.hide()
},
),

View File

@ -2,11 +2,13 @@ package com.github.diegoberaldin.raccoonforlemmy.core.notifications
object NotificationCenterContractKeys {
const val ChangeSortType = "changeSortType"
const val ChangeCommentSortType = "changeCommentSortType"
const val ChangeFeedType = "changeFeedType"
const val ChangeInboxType = "changeInboxType"
const val ChangeTheme = "changeTheme"
const val ChangeFontSize = "changeFontSize"
const val ChangeFontFamily = "changeFontFamily"
const val ChangeZombieInterval = "changeZombieInterval"
const val ChangeLanguage = "changeLanguage"
const val ChangePostLayout = "changePostLayout"
const val Logout = "logout"

View File

@ -1,6 +1,8 @@
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
data class SettingsModel(
val id: Long? = null,
@ -27,4 +29,5 @@ data class SettingsModel(
val autoLoadImages: Boolean = true,
val autoExpandComments: Boolean = true,
val hideNavigationBarWhileScrolling: Boolean = true,
val zombieModeInterval: Duration = 2.5.seconds,
) : JavaSerializable

View File

@ -8,6 +8,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.withContext
import kotlin.time.Duration.Companion.milliseconds
private object KeyStoreKeys {
const val UiTheme = "uiTheme"
@ -33,6 +34,7 @@ private object KeyStoreKeys {
const val UpvoteColor = "upvoteColor"
const val DownVoteColor = "downVoteColor"
const val HideNavigationBarWhileScrolling = "hideNavigationBarWhileScrolling"
const val ZombieModeInterval = "zombieModeInterval"
}
internal class DefaultSettingsRepository(
@ -71,7 +73,9 @@ internal class DefaultSettingsRepository(
upvoteColor = settings.upvoteColor?.toLong(),
downvoteColor = settings.downvoteColor?.toLong(),
hideNavigationBarWhileScrolling = if (settings.hideNavigationBarWhileScrolling) 1 else 0,
)
zombieModeInterval = settings.zombieModeInterval.inWholeMilliseconds,
)
}
override suspend fun getSettings(accountId: Long?): SettingsModel =
@ -104,6 +108,7 @@ internal class DefaultSettingsRepository(
upvoteColor = if (!keyStore.containsKey(KeyStoreKeys.UpvoteColor)) null else keyStore[KeyStoreKeys.UpvoteColor, 0],
downvoteColor = if (!keyStore.containsKey(KeyStoreKeys.DownVoteColor)) null else keyStore[KeyStoreKeys.DownVoteColor, 0],
hideNavigationBarWhileScrolling = keyStore[KeyStoreKeys.HideNavigationBarWhileScrolling, true],
zombieModeInterval = keyStore[KeyStoreKeys.ZombieModeInterval, 2500].milliseconds,
)
} else {
val entity = db.settingsQueries.getBy(accountId).executeAsOneOrNull()
@ -165,6 +170,10 @@ internal class DefaultSettingsRepository(
KeyStoreKeys.HideNavigationBarWhileScrolling,
settings.hideNavigationBarWhileScrolling
)
keyStore.save(
KeyStoreKeys.ZombieModeInterval,
settings.zombieModeInterval.inWholeMilliseconds,
)
} else {
db.settingsQueries.update(
theme = settings.theme?.toLong(),
@ -191,6 +200,7 @@ internal class DefaultSettingsRepository(
upvoteColor = settings.upvoteColor?.toLong(),
downvoteColor = settings.downvoteColor?.toLong(),
hideNavigationBarWhileScrolling = if (settings.hideNavigationBarWhileScrolling) 1L else 0L,
zombieModeInterval = settings.zombieModeInterval.inWholeMilliseconds,
)
}
}
@ -225,4 +235,5 @@ private fun GetBy.toModel() = SettingsModel(
upvoteColor = upvoteColor?.toInt(),
downvoteColor = downvoteColor?.toInt(),
hideNavigationBarWhileScrolling = hideNavigationBarWhileScrolling != 0L,
zombieModeInterval = zombieModeInterval.milliseconds,
)

View File

@ -23,6 +23,7 @@ CREATE TABLE SettingsEntity (
upvoteColor INTEGER DEFAULT NULL,
downvoteColor INTEGER DEFAULT NULL,
hideNavigationBarWhileScrolling INTEGER NOT NULL DEFAULT 1,
zombieModeInterval INTEGER NOT NULL DEFAULT 2500,
account_id INTEGER,
FOREIGN KEY (account_id) REFERENCES AccountEntity(id) ON DELETE CASCADE,
UNIQUE(account_id)
@ -53,6 +54,7 @@ INSERT OR IGNORE INTO SettingsEntity (
upvoteColor,
downvoteColor,
hideNavigationBarWhileScrolling,
zombieModeInterval,
account_id
) VALUES (
?,
@ -78,6 +80,7 @@ INSERT OR IGNORE INTO SettingsEntity (
?,
?,
?,
?,
?
);
@ -105,7 +108,8 @@ SET theme = ?,
fullHeightImages = ?,
upvoteColor = ?,
downvoteColor = ?,
hideNavigationBarWhileScrolling = ?
hideNavigationBarWhileScrolling = ?,
zombieModeInterval = ?
WHERE account_id = ?;
getBy:
@ -133,6 +137,7 @@ SELECT
fullHeightImages,
upvoteColor,
downvoteColor,
hideNavigationBarWhileScrolling
hideNavigationBarWhileScrolling,
zombieModeInterval
FROM SettingsEntity
WHERE account_id = ?;

View File

@ -1,2 +1,5 @@
ALTER TABLE SettingsEntity
ADD COLUMN hideNavigationBarWhileScrolling INTEGER NOT NULL DEFAULT 1;
ALTER TABLE SettingsEntity
ADD COLUMN zombieModeInterval INTEGER NOT NULL DEFAULT 2500;

View File

@ -15,7 +15,8 @@ import io.ktor.utils.io.core.readBytes
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.withContext
import kotlin.math.roundToInt
import kotlin.math.round
import kotlin.time.Duration
@Composable
fun String.toLanguageName() = when (this) {
@ -43,10 +44,75 @@ fun Int.getPrettyNumber(
millionLabel: String,
thousandLabel: String,
): String {
val value = this
return when {
this > 1_000_000 -> (((this / 1_000_000.0) * 10).roundToInt() / 10.0).toString() + millionLabel
this > 1_000 -> (((this / 1_000.0) * 10).roundToInt() / 10.0).toString() + thousandLabel
else -> this.toString()
value > 1_000_000 -> buildString {
val rounded = round((value / 1_000_000.0) * 10) / 10
if (rounded % 1 <= 0) {
append(rounded.toInt())
} else {
append(rounded)
}
append(millionLabel)
}
value > 1_000 -> buildString {
val rounded = round((value / 1_000.0) * 10) / 10
if (rounded % 1 <= 0) {
append(rounded.toInt())
} else {
append(rounded)
}
append(thousandLabel)
}
else -> buildString {
append(value)
}
}
}
fun Duration.getPrettyDuration(
secondsLabel: String,
minutesLabel: String,
hoursLabel: String,
): String = when {
inWholeHours > 0 -> buildString {
append(inWholeHours)
append(hoursLabel)
val remainderMinutes = inWholeMinutes % 60
val remainderSeconds = inWholeSeconds % 60
if (remainderMinutes > 0 || remainderSeconds > 0) {
append(" ")
append(remainderMinutes)
append(minutesLabel)
}
if (remainderSeconds > 0) {
append(" ")
append(remainderSeconds)
append(secondsLabel)
}
}
inWholeMinutes > 0 -> buildString {
append(inWholeMinutes)
append(minutesLabel)
val remainderSeconds = inWholeSeconds % 60
if (remainderSeconds > 0) {
append(" ")
append(remainderSeconds)
append(secondsLabel)
}
}
else -> buildString {
val rounded = round((inWholeMilliseconds / 1000.0) * 10.0) / 10.0
if (rounded % 1 <= 0) {
append(rounded.toInt())
} else {
append(rounded)
}
append(secondsLabel)
}
}

View File

@ -0,0 +1,48 @@
package com.github.diegoberaldin.raccoonforlemmy.core.utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
interface ZombieModeHelper {
val index: Flow<Int>
fun start(
initialValue: Int = 0,
interval: Duration = 2.5.seconds,
)
fun pause()
}
internal class DefaultZombieModeHelper : ZombieModeHelper {
private val scope = CoroutineScope(SupervisorJob())
override val index = MutableStateFlow(-1)
private var delayInterval: Duration = 2.5.seconds
private var job: Job? = null
override fun start(initialValue: Int, interval: Duration) {
index.value = initialValue
delayInterval = interval
job = scope.launch {
while (isActive) {
delay(delayInterval)
index.update { (it + 1).coerceAtLeast(0) }
}
}
}
override fun pause() {
job?.cancel()
index.value = -1
}
}

View File

@ -0,0 +1,11 @@
package com.github.diegoberaldin.raccoonforlemmy.core.utils.di
import com.github.diegoberaldin.raccoonforlemmy.core.utils.DefaultZombieModeHelper
import com.github.diegoberaldin.raccoonforlemmy.core.utils.ZombieModeHelper
import org.koin.dsl.module
val utilsModule = module {
factory<ZombieModeHelper> {
DefaultZombieModeHelper()
}
}

View File

@ -24,6 +24,7 @@ val homeTabModule = module {
shareHelper = get(),
notificationCenter = get(),
hapticFeedback = get(),
zombieModeHelper = get(),
)
}
}

View File

@ -28,6 +28,8 @@ interface PostListMviModel :
data class MarkAsRead(val id: Int) : Intent
data class Hide(val id: Int) : Intent
data object ClearRead : Intent
data class StartZombieMode(val index: Int) : Intent
data object PauseZombieMode : Intent
}
data class UiState(
@ -46,9 +48,11 @@ interface PostListMviModel :
val fullHeightImages: Boolean = true,
val separateUpAndDownVotes: Boolean = false,
val autoLoadImages: Boolean = true,
val zombieModeActive: Boolean = false,
)
sealed interface Effect {
data object BackToTop : Effect
data class ZombieModeTick(val index: Int) : Effect
}
}

View File

@ -20,6 +20,8 @@ import androidx.compose.material.icons.filled.ArrowCircleDown
import androidx.compose.material.icons.filled.ArrowCircleUp
import androidx.compose.material.icons.filled.ClearAll
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.Sync
import androidx.compose.material.icons.filled.SyncDisabled
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
@ -129,11 +131,17 @@ class PostListScreen : Screen {
}.launchIn(this)
}
LaunchedEffect(model) {
model.effects.onEach {
when (it) {
model.effects.onEach { effect ->
when (effect) {
PostListMviModel.Effect.BackToTop -> {
lazyListState.scrollToItem(0)
}
is PostListMviModel.Effect.ZombieModeTick -> {
if (effect.index >= 0) {
lazyListState.scrollToItem(effect.index)
}
}
}
}.launchIn(this)
}
@ -210,6 +218,24 @@ class PostListScreen : Screen {
FloatingActionButtonMenu(
modifier = Modifier.padding(bottom = Dimensions.topBarHeight),
items = buildList {
if (uiState.zombieModeActive) {
this += FloatingActionButtonMenuItem(
icon = Icons.Default.SyncDisabled,
text = stringResource(MR.strings.action_deactivate_zombie_mode),
onSelected = rememberCallback(model) {
model.reduce(PostListMviModel.Intent.PauseZombieMode)
},
)
} else {
this += FloatingActionButtonMenuItem(
icon = Icons.Default.Sync,
text = stringResource(MR.strings.action_activate_zombie_mode),
onSelected = rememberCallback(model) {
val idx = lazyListState.firstVisibleItemIndex
model.reduce(PostListMviModel.Intent.StartZombieMode(idx))
},
)
}
this += FloatingActionButtonMenuItem(
icon = Icons.Default.ExpandLess,
text = stringResource(MR.strings.action_back_to_top),
@ -267,6 +293,7 @@ class PostListScreen : Screen {
) {
LazyColumn(
state = lazyListState,
userScrollEnabled = !uiState.zombieModeActive,
) {
if (uiState.posts.isEmpty() && uiState.loading) {
items(5) {

View File

@ -8,6 +8,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationC
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.utils.HapticFeedback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.ShareHelper
import com.github.diegoberaldin.raccoonforlemmy.core.utils.ZombieModeHelper
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.ApiConfigurationRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.ListingType
@ -36,6 +37,7 @@ class PostListViewModel(
private val settingsRepository: SettingsRepository,
private val notificationCenter: NotificationCenter,
private val hapticFeedback: HapticFeedback,
private val zombieModeHelper: ZombieModeHelper,
) : PostListMviModel,
MviModel<PostListMviModel.Intent, PostListMviModel.UiState, PostListMviModel.Effect> by mvi {
@ -108,6 +110,10 @@ class PostListViewModel(
}
}.launchIn(this)
zombieModeHelper.index.onEach { index ->
mvi.emitEffect(PostListMviModel.Effect.ZombieModeTick(index))
}.launchIn(this)
val auth = identityRepository.authToken.value.orEmpty()
val user = siteRepository.getCurrentUser(auth)
mvi.updateState { it.copy(currentUserId = user?.id ?: 0) }
@ -184,7 +190,21 @@ class PostListViewModel(
}
PostListMviModel.Intent.ClearRead -> clearRead()
is PostListMviModel.Intent.Hide -> hide(post = uiState.value.posts.first { it.id == intent.id })
is PostListMviModel.Intent.Hide -> {
uiState.value.posts.firstOrNull { it.id == intent.id }?.also { post ->
hide(post = post)
}
}
PostListMviModel.Intent.PauseZombieMode -> {
mvi.updateState { it.copy(zombieModeActive = false) }
zombieModeHelper.pause()
}
is PostListMviModel.Intent.StartZombieMode -> {
mvi.updateState { it.copy(zombieModeActive = true) }
zombieModeHelper.start(intent.index)
}
}
}

View File

@ -9,6 +9,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.UiTheme
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.ListingType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
interface SettingsMviModel :
MviModel<SettingsMviModel.Intent, SettingsMviModel.UiState, SettingsMviModel.Effect>,
@ -39,6 +41,7 @@ interface SettingsMviModel :
data class ChangeAutoExpandComments(val value: Boolean) : Intent
data class ChangeFullHeightImages(val value: Boolean) : Intent
data class ChangeHideNavigationBarWhileScrolling(val value: Boolean) : Intent
data class ChangeZombieModeInterval(val value: Duration) : Intent
}
data class UiState(
@ -68,6 +71,7 @@ interface SettingsMviModel :
val autoExpandComments: Boolean = false,
val fullHeightImages: Boolean = false,
val hideNavigationBarWhileScrolling: Boolean = true,
val zombieModeInterval: Duration = 2.5.seconds,
)
sealed interface Effect

View File

@ -52,6 +52,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getDrawerCoordi
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.ColorBottomSheet
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.ColorPickerDialog
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.DurationBottomSheet
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.FontFamilyBottomSheet
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.FontScaleBottomSheet
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.LanguageBottomSheet
@ -61,6 +62,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.SortBottomS
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.ThemeBottomSheet
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.utils.getPrettyDuration
import com.github.diegoberaldin.raccoonforlemmy.core.utils.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.rememberCallbackArgs
@ -85,6 +87,7 @@ import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlin.time.Duration
class SettingsScreen : Screen {
@ -229,12 +232,25 @@ class SettingsScreen : Screen {
)
)
}
}, key, NotificationCenterContractKeys.ChangeSortType
}, key, NotificationCenterContractKeys.ChangeCommentSortType
)
notificationCenter.addObserver(
{
infoDialogOpened = false
}, key, NotificationCenterContractKeys.CloseDialog
},
key, NotificationCenterContractKeys.CloseDialog,
)
notificationCenter.addObserver(
{
(it as? Duration)?.also { value ->
model.reduce(
SettingsMviModel.Intent.ChangeZombieModeInterval(
value,
)
)
}
},
key, NotificationCenterContractKeys.ChangeZombieInterval
)
}
@ -460,6 +476,7 @@ class SettingsScreen : Screen {
onTap = rememberCallback {
val sheet = SortBottomSheet(
expandTop = true,
contract = NotificationCenterContractKeys.ChangeSortType,
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
},
@ -471,6 +488,7 @@ class SettingsScreen : Screen {
value = uiState.defaultCommentSortType.toReadableName(),
onTap = rememberCallback {
val sheet = SortBottomSheet(
contract = NotificationCenterContractKeys.ChangeCommentSortType,
values = listOf(
SortType.Hot,
SortType.Top.Generic,
@ -488,6 +506,20 @@ class SettingsScreen : Screen {
title = stringResource(MR.strings.settings_section_behaviour),
)
// zombie mode interval
SettingsRow(
title = stringResource(MR.strings.settings_zombie_mode_interval),
value = uiState.zombieModeInterval.getPrettyDuration(
secondsLabel = stringResource(MR.strings.post_second_short),
minutesLabel = stringResource(MR.strings.post_minute_short),
hoursLabel = stringResource(MR.strings.post_hour_short),
),
onTap = rememberCallback {
val sheet = DurationBottomSheet()
navigationCoordinator.getBottomNavigator()?.show(sheet)
},
)
// swipe actions
SettingsSwitchRow(
title = stringResource(MR.strings.settings_enable_swipe_actions),

View File

@ -28,6 +28,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlin.time.Duration
class SettingsViewModel(
private val mvi: DefaultMviModel<SettingsMviModel.Intent, SettingsMviModel.UiState, SettingsMviModel.Effect>,
@ -195,6 +196,10 @@ class SettingsViewModel(
is SettingsMviModel.Intent.ChangeHideNavigationBarWhileScrolling -> changeHideNavigationBarWhileScrolling(
intent.value
)
is SettingsMviModel.Intent.ChangeZombieModeInterval -> changeZombieModeInterval(
intent.value
)
}
}
@ -441,6 +446,16 @@ class SettingsViewModel(
}
}
private fun changeZombieModeInterval(value: Duration) {
mvi.updateState { it.copy(zombieModeInterval = value) }
mvi.scope?.launch {
val settings = settingsRepository.currentSettings.value.copy(
zombieModeInterval = value
)
saveSettings(settings)
}
}
private suspend fun saveSettings(settings: SettingsModel) {
val accountId = accountRepository.getActive()?.id
settingsRepository.updateSettings(settings, accountId)

View File

@ -1,217 +1,220 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="action_back_to_top">Back to top</string>
<string name="action_chat">Send message</string>
<string name="action_clear_read">Clear read</string>
<string name="action_create_post">Create post</string>
<string name="action_reply">Reply</string>
<string name="button_close">Close</string>
<string name="button_confirm">Confirm</string>
<string name="button_load">Load</string>
<string name="button_reset">Reset</string>
<string name="button_retry">Retry</string>
<string name="comment_action_delete">Delete</string>
<string name="community_detail_block">Block</string>
<string name="community_detail_block_instance">Block instance</string>
<string name="community_detail_info">Community info</string>
<string name="community_detail_instance_info">Instance details</string>
<string name="community_info_comments">comments</string>
<string name="community_info_daily_active_users">active users (day)</string>
<string name="community_info_monthly_active_users">active users (month)</string>
<string name="community_info_posts">posts</string>
<string name="community_info_subscribers">subscribers</string>
<string name="community_info_weekly_active_users">active users (week)</string>
<string name="create_comment_body">Comment body</string>
<string name="create_comment_title">New comment</string>
<string name="create_post_body">Post body</string>
<string name="create_post_name">Post title</string>
<string name="create_post_nsfw">NSFW</string>
<string name="create_post_tab_editor">Editor</string>
<string name="create_post_tab_preview">Preview</string>
<string name="create_post_title">New post</string>
<string name="create_post_url">URL</string>
<string name="create_report_placeholder">Report text (optional)</string>
<string name="create_report_title_comment">Report comment</string>
<string name="create_report_title_post">Report post</string>
<string name="dialog_raw_content_text">Text</string>
<string name="dialog_raw_content_title">Title</string>
<string name="dialog_raw_content_url">URL</string>
<string name="dialog_title_change_instance">Change instance</string>
<string name="dialog_title_raw_content">Raw content</string>
<string name="explore_result_type_all">All</string>
<string name="explore_result_type_comments">Comments</string>
<string name="explore_result_type_communities">Communities</string>
<string name="explore_result_type_posts">Posts</string>
<string name="explore_result_type_users">Users</string>
<string name="explore_search_placeholder">Search</string>
<string name="home_instance_via">via %1$s</string>
<string name="home_listing_title">Feeds</string>
<string name="home_listing_type_all">All</string>
<string name="home_listing_type_local">Local</string>
<string name="home_listing_type_subscribed">Subscribed</string>
<string name="home_sort_title">Sort by</string>
<string name="home_sort_type_active">Active</string>
<string name="home_sort_type_controversial">Controversial</string>
<string name="home_sort_type_hot">Hot</string>
<string name="home_sort_type_most_comments">Most comments</string>
<string name="home_sort_type_new">New</string>
<string name="home_sort_type_new_comments">New comments</string>
<string name="home_sort_type_old">Old</string>
<string name="home_sort_type_scaled">Scaled</string>
<string name="home_sort_type_top">Top</string>
<string name="home_sort_type_top_12_hours">Top 12 hours</string>
<string name="home_sort_type_top_12_hours_short">12h</string>
<string name="home_sort_type_top_6_hours">Top 6 hours</string>
<string name="home_sort_type_top_6_hours_short">6h</string>
<string name="home_sort_type_top_day">Top day</string>
<string name="home_sort_type_top_day_short">day</string>
<string name="home_sort_type_top_hour">Top hour</string>
<string name="home_sort_type_top_hour_short">1h</string>
<string name="home_sort_type_top_month">Top month</string>
<string name="home_sort_type_top_month_short">month</string>
<string name="home_sort_type_top_week">Top week</string>
<string name="home_sort_type_top_week_short">week</string>
<string name="home_sort_type_top_year">Top year</string>
<string name="home_sort_type_top_year_short">year</string>
<string name="inbox_chat_message">Message</string>
<string name="inbox_item_mention">mentioned you in</string>
<string name="inbox_item_reply_comment">replied to your comment in</string>
<string name="inbox_item_reply_post">replied to your post in</string>
<string name="inbox_listing_type_all">All</string>
<string name="inbox_listing_type_title">Inbox type</string>
<string name="inbox_listing_type_unread">Unread</string>
<string name="inbox_not_logged_message">You are currently not logged in.\nPlease add an account
from the profile screen to see your inbox.
</string>
<string name="inbox_section_mentions">Mentions</string>
<string name="inbox_section_messages">Messages</string>
<string name="inbox_section_replies">Replies</string>
<string name="instance_detail_communities">Communities</string>
<string name="instance_detail_title">Instance: %1$s</string>
<string name="lang">en</string>
<string name="language_de" translatable="false">Deutsch</string>
<string name="language_el" translatable="false">Ελληνικά</string>
<string name="language_en" translatable="false">English</string>
<string name="language_es" translatable="false">Español</string>
<string name="language_fr" translatable="false">Français</string>
<string name="language_it" translatable="false">Italiano</string>
<string name="language_pt" translatable="false">Português</string>
<string name="language_ro" translatable="false">Română</string>
<string name="login_field_instance_name">Instance name</string>
<string name="login_field_label_optional">(optional)</string>
<string name="login_field_password">Password</string>
<string name="login_field_token">TOTP 2FA token</string>
<string name="login_field_user_name">Username (or email)</string>
<string name="manage_accounts_button_add">Add account</string>
<string name="manage_accounts_title">Manage accounts</string>
<string name="manage_subscriptions_header_multicommunities">Multi-communities</string>
<string name="manage_subscriptions_header_subscriptions">Subscriptions</string>
<string name="message_empty_comments">It\'s too silent there.\nWould you like to be the one who
writes the first comment?
</string>
<string name="message_empty_list">No items to display</string>
<string name="message_error_loading_comments">An error has occurred loading comments.</string>
<string name="message_generic_error">Generic error</string>
<string name="message_image_loading_error">Image loading error</string>
<string name="message_invalid_field">Invalid field</string>
<string name="message_missing_field">Missing field</string>
<string name="message_operation_successful">Operation completed succcessfully</string>
<string name="multi_community_editor_communities">Communities</string>
<string name="multi_community_editor_icon">Icon</string>
<string name="multi_community_editor_name">Name</string>
<string name="multi_community_editor_title">Multi-community editor</string>
<string name="navigation_drawer_anonymous">Anonymous</string>
<string name="navigation_drawer_title_bookmarks">Saved</string>
<string name="navigation_drawer_title_subscriptions">Subscriptions</string>
<string name="navigation_home">Posts</string>
<string name="navigation_inbox">Inbox</string>
<string name="navigation_profile">Profile</string>
<string name="navigation_search">Explore</string>
<string name="navigation_settings">Settings</string>
<string name="post_action_edit">Edit</string>
<string name="post_action_hide">Hide</string>
<string name="post_action_report">Report…</string>
<string name="post_action_see_raw">View raw…</string>
<string name="post_action_share">Share…</string>
<string name="post_detail_cross_posts">also posted to:</string>
<string name="post_detail_load_more_comments">Load more comments…</string>
<string name="post_hour_short">h</string>
<string name="post_minute_short">m</string>
<string name="post_second_short">s</string>
<string name="profile_button_login">Login</string>
<string name="profile_day_short">d</string>
<string name="profile_million_short">m</string>
<string name="profile_month_short">m</string>
<string name="profile_not_logged_message">You are currently not logged in.\nPlease add an
account to continue.
</string>
<string name="profile_section_comments">Comments</string>
<string name="profile_section_posts">Posts</string>
<string name="profile_thousand_short">k</string>
<string name="profile_year_short">y</string>
<string name="settings_about">About this app…</string>
<string name="settings_about_app_version">App version</string>
<string name="settings_about_changelog">View full changelog</string>
<string name="settings_about_report_github">Report a bug (GitHub)</string>
<string name="settings_about_report_email">Report a bug (email)</string>
<string name="settings_about_view_github">View on GitHub</string>
<string name="settings_about_view_lemmy">Lemmy community</string>
<string name="settings_auto_expand_comments">Automatically expand comments</string>
<string name="settings_auto_load_images">Automatically load images</string>
<string name="settings_blur_nsfw">Blur NSFW images</string>
<string name="settings_color_aquamarine">🐬 Distracted dolphin</string>
<string name="settings_color_banana">🦔 Hilarious hedgehog</string>
<string name="settings_color_blue">🐳 Witty whale</string>
<string name="settings_color_custom">Custom</string>
<string name="settings_color_dialog_alpha">A</string>
<string name="settings_color_dialog_blue">B</string>
<string name="settings_color_dialog_green">G</string>
<string name="settings_color_dialog_red">R</string>
<string name="settings_color_dialog_title">Pick a color</string>
<string name="settings_color_gray">🦝 Ravenous raccoon</string>
<string name="settings_color_green">🐸 Frolicsome frog</string>
<string name="settings_color_orange">🦊 Fiery fox</string>
<string name="settings_color_pink">🦄 Unique unicorn</string>
<string name="settings_color_purple">🐙 Oceanic octopus</string>
<string name="settings_color_red">🦀 Crunchy crab</string>
<string name="settings_color_white">🐼 Bambling bear</string>
<string name="settings_content_font_large">Large</string>
<string name="settings_content_font_larger">Extra large</string>
<string name="settings_content_font_largest">Double extra large</string>
<string name="settings_content_font_normal">Normal</string>
<string name="settings_content_font_scale">Content text size</string>
<string name="settings_content_font_small">Small</string>
<string name="settings_content_font_smaller">Extra small</string>
<string name="settings_content_font_smallest">Double extra small</string>
<string name="settings_custom_seed_color">Custom theme color</string>
<string name="settings_default_comment_sort_type">Default comment sort type</string>
<string name="settings_default_listing_type">Default feed type</string>
<string name="settings_default_post_sort_type">Default post sort type</string>
<string name="settings_downvote_color">Downvote color</string>
<string name="settings_dynamic_colors">Use dynamic colors</string>
<string name="settings_enable_crash_report">Enable crash reporting</string>
<string name="settings_enable_swipe_actions">Enable swipe actions</string>
<string name="settings_full_height_images">Full height images</string>
<string name="settings_include_nsfw">Include NSFW contents</string>
<string name="settings_language">Language</string>
<string name="settings_navigation_bar_titles_visible">Show navigation bar titles</string>
<string name="settings_open_url_external">Open URLs in external browser</string>
<string name="settings_post_layout">Post layout</string>
<string name="settings_post_layout_card">Card</string>
<string name="settings_post_layout_compact">Compact</string>
<string name="settings_post_layout_full">Full</string>
<string name="settings_section_appearance">Look and feel</string>
<string name="settings_section_behaviour">Behaviour</string>
<string name="settings_section_debug">Debug</string>
<string name="settings_section_feed">Posts and comments</string>
<string name="settings_section_nsfw">NSFW</string>
<string name="settings_separate_up_and_downvotes">Separate upvotes / downvotes</string>
<string name="settings_theme_black">Pure black</string>
<string name="settings_theme_dark">Dark</string>
<string name="settings_theme_light">Light</string>
<string name="settings_ui_font_family">UI font</string>
<string name="settings_ui_font_scale">UI text size</string>
<string name="settings_ui_theme">UI theme</string>
<string name="settings_upvote_color">Upvote color</string>
<string name="settings_hide_navigation_bar">Hide navigation bar when scrolling</string>
<string name="action_back_to_top">Back to top</string>
<string name="action_chat">Send message</string>
<string name="action_clear_read">Clear read</string>
<string name="action_create_post">Create post</string>
<string name="action_reply">Reply</string>
<string name="action_activate_zombie_mode">Activate zombie mode</string>
<string name="action_deactivate_zombie_mode">Deactivate zombie mode</string>
<string name="button_close">Close</string>
<string name="button_confirm">Confirm</string>
<string name="button_load">Load</string>
<string name="button_reset">Reset</string>
<string name="button_retry">Retry</string>
<string name="comment_action_delete">Delete</string>
<string name="community_detail_block">Block</string>
<string name="community_detail_block_instance">Block instance</string>
<string name="community_detail_info">Community info</string>
<string name="community_detail_instance_info">Instance details</string>
<string name="community_info_comments">comments</string>
<string name="community_info_daily_active_users">active users (day)</string>
<string name="community_info_monthly_active_users">active users (month)</string>
<string name="community_info_posts">posts</string>
<string name="community_info_subscribers">subscribers</string>
<string name="community_info_weekly_active_users">active users (week)</string>
<string name="create_comment_body">Comment body</string>
<string name="create_comment_title">New comment</string>
<string name="create_post_body">Post body</string>
<string name="create_post_name">Post title</string>
<string name="create_post_nsfw">NSFW</string>
<string name="create_post_tab_editor">Editor</string>
<string name="create_post_tab_preview">Preview</string>
<string name="create_post_title">New post</string>
<string name="create_post_url">URL</string>
<string name="create_report_placeholder">Report text (optional)</string>
<string name="create_report_title_comment">Report comment</string>
<string name="create_report_title_post">Report post</string>
<string name="dialog_raw_content_text">Text</string>
<string name="dialog_raw_content_title">Title</string>
<string name="dialog_raw_content_url">URL</string>
<string name="dialog_title_change_instance">Change instance</string>
<string name="dialog_title_raw_content">Raw content</string>
<string name="explore_result_type_all">All</string>
<string name="explore_result_type_comments">Comments</string>
<string name="explore_result_type_communities">Communities</string>
<string name="explore_result_type_posts">Posts</string>
<string name="explore_result_type_users">Users</string>
<string name="explore_search_placeholder">Search</string>
<string name="home_instance_via">via %1$s</string>
<string name="home_listing_title">Feeds</string>
<string name="home_listing_type_all">All</string>
<string name="home_listing_type_local">Local</string>
<string name="home_listing_type_subscribed">Subscribed</string>
<string name="home_sort_title">Sort by</string>
<string name="home_sort_type_active">Active</string>
<string name="home_sort_type_controversial">Controversial</string>
<string name="home_sort_type_hot">Hot</string>
<string name="home_sort_type_most_comments">Most comments</string>
<string name="home_sort_type_new">New</string>
<string name="home_sort_type_new_comments">New comments</string>
<string name="home_sort_type_old">Old</string>
<string name="home_sort_type_scaled">Scaled</string>
<string name="home_sort_type_top">Top</string>
<string name="home_sort_type_top_12_hours">Top 12 hours</string>
<string name="home_sort_type_top_12_hours_short">12h</string>
<string name="home_sort_type_top_6_hours">Top 6 hours</string>
<string name="home_sort_type_top_6_hours_short">6h</string>
<string name="home_sort_type_top_day">Top day</string>
<string name="home_sort_type_top_day_short">day</string>
<string name="home_sort_type_top_hour">Top hour</string>
<string name="home_sort_type_top_hour_short">1h</string>
<string name="home_sort_type_top_month">Top month</string>
<string name="home_sort_type_top_month_short">month</string>
<string name="home_sort_type_top_week">Top week</string>
<string name="home_sort_type_top_week_short">week</string>
<string name="home_sort_type_top_year">Top year</string>
<string name="home_sort_type_top_year_short">year</string>
<string name="inbox_chat_message">Message</string>
<string name="inbox_item_mention">mentioned you in</string>
<string name="inbox_item_reply_comment">replied to your comment in</string>
<string name="inbox_item_reply_post">replied to your post in</string>
<string name="inbox_listing_type_all">All</string>
<string name="inbox_listing_type_title">Inbox type</string>
<string name="inbox_listing_type_unread">Unread</string>
<string name="inbox_not_logged_message">You are currently not logged in.\nPlease add an account
from the profile screen to see your inbox.
</string>
<string name="inbox_section_mentions">Mentions</string>
<string name="inbox_section_messages">Messages</string>
<string name="inbox_section_replies">Replies</string>
<string name="instance_detail_communities">Communities</string>
<string name="instance_detail_title">Instance: %1$s</string>
<string name="lang">en</string>
<string name="language_de" translatable="false">Deutsch</string>
<string name="language_el" translatable="false">Ελληνικά</string>
<string name="language_en" translatable="false">English</string>
<string name="language_es" translatable="false">Español</string>
<string name="language_fr" translatable="false">Français</string>
<string name="language_it" translatable="false">Italiano</string>
<string name="language_pt" translatable="false">Português</string>
<string name="language_ro" translatable="false">Română</string>
<string name="login_field_instance_name">Instance name</string>
<string name="login_field_label_optional">(optional)</string>
<string name="login_field_password">Password</string>
<string name="login_field_token">TOTP 2FA token</string>
<string name="login_field_user_name">Username (or email)</string>
<string name="manage_accounts_button_add">Add account</string>
<string name="manage_accounts_title">Manage accounts</string>
<string name="manage_subscriptions_header_multicommunities">Multi-communities</string>
<string name="manage_subscriptions_header_subscriptions">Subscriptions</string>
<string name="message_empty_comments">It\'s too silent there.\nWould you like to be the one who
writes the first comment?
</string>
<string name="message_empty_list">No items to display</string>
<string name="message_error_loading_comments">An error has occurred loading comments.</string>
<string name="message_generic_error">Generic error</string>
<string name="message_image_loading_error">Image loading error</string>
<string name="message_invalid_field">Invalid field</string>
<string name="message_missing_field">Missing field</string>
<string name="message_operation_successful">Operation completed succcessfully</string>
<string name="multi_community_editor_communities">Communities</string>
<string name="multi_community_editor_icon">Icon</string>
<string name="multi_community_editor_name">Name</string>
<string name="multi_community_editor_title">Multi-community editor</string>
<string name="navigation_drawer_anonymous">Anonymous</string>
<string name="navigation_drawer_title_bookmarks">Saved</string>
<string name="navigation_drawer_title_subscriptions">Subscriptions</string>
<string name="navigation_home">Posts</string>
<string name="navigation_inbox">Inbox</string>
<string name="navigation_profile">Profile</string>
<string name="navigation_search">Explore</string>
<string name="navigation_settings">Settings</string>
<string name="post_action_edit">Edit</string>
<string name="post_action_hide">Hide</string>
<string name="post_action_report">Report…</string>
<string name="post_action_see_raw">View raw…</string>
<string name="post_action_share">Share…</string>
<string name="post_detail_cross_posts">also posted to:</string>
<string name="post_detail_load_more_comments">Load more comments…</string>
<string name="post_hour_short">h</string>
<string name="post_minute_short">m</string>
<string name="post_second_short">s</string>
<string name="profile_button_login">Login</string>
<string name="profile_day_short">d</string>
<string name="profile_million_short">m</string>
<string name="profile_month_short">m</string>
<string name="profile_not_logged_message">You are currently not logged in.\nPlease add an
account to continue.
</string>
<string name="profile_section_comments">Comments</string>
<string name="profile_section_posts">Posts</string>
<string name="profile_thousand_short">k</string>
<string name="profile_year_short">y</string>
<string name="settings_about">About this app…</string>
<string name="settings_about_app_version">App version</string>
<string name="settings_about_changelog">View full changelog</string>
<string name="settings_about_report_github">Report a bug (GitHub)</string>
<string name="settings_about_report_email">Report a bug (email)</string>
<string name="settings_about_view_github">View on GitHub</string>
<string name="settings_about_view_lemmy">Lemmy community</string>
<string name="settings_auto_expand_comments">Automatically expand comments</string>
<string name="settings_auto_load_images">Automatically load images</string>
<string name="settings_blur_nsfw">Blur NSFW images</string>
<string name="settings_color_aquamarine">🐬 Distracted dolphin</string>
<string name="settings_color_banana">🦔 Hilarious hedgehog</string>
<string name="settings_color_blue">🐳 Witty whale</string>
<string name="settings_color_custom">Custom</string>
<string name="settings_color_dialog_alpha">A</string>
<string name="settings_color_dialog_blue">B</string>
<string name="settings_color_dialog_green">G</string>
<string name="settings_color_dialog_red">R</string>
<string name="settings_color_dialog_title">Pick a color</string>
<string name="settings_color_gray">🦝 Ravenous raccoon</string>
<string name="settings_color_green">🐸 Frolicsome frog</string>
<string name="settings_color_orange">🦊 Fiery fox</string>
<string name="settings_color_pink">🦄 Unique unicorn</string>
<string name="settings_color_purple">🐙 Oceanic octopus</string>
<string name="settings_color_red">🦀 Crunchy crab</string>
<string name="settings_color_white">🐼 Bambling bear</string>
<string name="settings_content_font_large">Large</string>
<string name="settings_content_font_larger">Extra large</string>
<string name="settings_content_font_largest">Double extra large</string>
<string name="settings_content_font_normal">Normal</string>
<string name="settings_content_font_scale">Content text size</string>
<string name="settings_content_font_small">Small</string>
<string name="settings_content_font_smaller">Extra small</string>
<string name="settings_content_font_smallest">Double extra small</string>
<string name="settings_custom_seed_color">Custom theme color</string>
<string name="settings_default_comment_sort_type">Default comment sort type</string>
<string name="settings_default_listing_type">Default feed type</string>
<string name="settings_default_post_sort_type">Default post sort type</string>
<string name="settings_downvote_color">Downvote color</string>
<string name="settings_dynamic_colors">Use dynamic colors</string>
<string name="settings_enable_crash_report">Enable crash reporting</string>
<string name="settings_enable_swipe_actions">Enable swipe actions</string>
<string name="settings_full_height_images">Full height images</string>
<string name="settings_include_nsfw">Include NSFW contents</string>
<string name="settings_language">Language</string>
<string name="settings_navigation_bar_titles_visible">Show navigation bar titles</string>
<string name="settings_open_url_external">Open URLs in external browser</string>
<string name="settings_post_layout">Post layout</string>
<string name="settings_post_layout_card">Card</string>
<string name="settings_post_layout_compact">Compact</string>
<string name="settings_post_layout_full">Full</string>
<string name="settings_section_appearance">Look and feel</string>
<string name="settings_section_behaviour">Behaviour</string>
<string name="settings_section_debug">Debug</string>
<string name="settings_section_feed">Posts and comments</string>
<string name="settings_section_nsfw">NSFW</string>
<string name="settings_separate_up_and_downvotes">Separate upvotes / downvotes</string>
<string name="settings_theme_black">Pure black</string>
<string name="settings_theme_dark">Dark</string>
<string name="settings_theme_light">Light</string>
<string name="settings_ui_font_family">UI font</string>
<string name="settings_ui_font_scale">UI text size</string>
<string name="settings_ui_theme">UI theme</string>
<string name="settings_upvote_color">Upvote color</string>
<string name="settings_hide_navigation_bar">Hide navigation bar when scrolling</string>
<string name="settings_zombie_mode_interval">Zombie mode interval duration</string>
</resources>

View File

@ -5,6 +5,8 @@
<string name="action_clear_read">Gelesene löschen</string>
<string name="action_create_post">Beitrag erstellen</string>
<string name="action_reply">Antwort</string>
<string name="action_activate_zombie_mode">Aktivieren den Zombie-Modus</string>
<string name="action_deactivate_zombie_mode">Deaktivieren den Zombie-Modus</string>
<string name="button_close">Schließen</string>
<string name="button_confirm">Bestätigen</string>
<string name="button_load">Belastung</string>
@ -210,4 +212,5 @@
<string name="settings_ui_theme">ui-Thema</string>
<string name="settings_upvote_color">Farbe für Upvotes</string>
<string name="settings_hide_navigation_bar">Navigationsleiste beim Scrollen ausblenden</string>
<string name="settings_zombie_mode_interval">Dauer des Intervalls des Zombie-Modus</string>
</resources>

View File

@ -1,20 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="action_back_to_top">Πίσω στην κορυφή</string>
<string name="action_chat">Αποστολή μηνύματος</string>
<string name="action_clear_read">Εκκαθάριση αναγνωσμένων</string>
<string name="action_create_post">Δημιουργία ανάρτησης</string>
<string name="action_reply">Απάντηση</string>
<string name="button_close">Κλείσιμο</string>
<string name="button_confirm">Επιβεβαίωση</string>
<string name="button_load">Φόρτωση</string>
<string name="button_reset">Επαναφορά</string>
<string name="action_chat">Αποστείλετε μήνυμα</string>
<string name="action_clear_read">Εκκαθαρίστε αναγνωσμένα</string>
<string name="action_create_post">Δημιουργήστε ανάρτηση</string>
<string name="action_activate_zombie_mode">Ενεργοποιήστε λειτουργία ζόμπι</string>
<string name="action_deactivate_zombie_mode">Απενεργοποιήστε λειτουργία ζόμπι</string>
<string name="action_reply">Απάντηστε</string>
<string name="button_close">Κλείσε</string>
<string name="button_confirm">Επιβεβαίωσε</string>
<string name="button_load">Φόρτωσε</string>
<string name="button_reset">Επανάφερε</string>
<string name="button_retry">Προσπάθησε ξανά</string>
<string name="comment_action_delete">Διαγραφή</string>
<string name="community_detail_block">Φραγή</string>
<string name="community_detail_block_instance">Φραγή παράδειγματος</string>
<string name="comment_action_delete">Διάγραψε</string>
<string name="community_detail_block">Απόκλεισε</string>
<string name="community_detail_block_instance">Απόκλεισε διακομιστή</string>
<string name="community_detail_info">Πληροφορίες κοινότητας</string>
<string name="community_detail_instance_info">Λεπτομέρειες παραδείγματος</string>
<string name="community_detail_instance_info">Πληροφορίες διακομιστή</string>
<string name="community_info_comments">σχόλια</string>
<string name="community_info_daily_active_users">ενεργοί χρήστες (ημέρα)</string>
<string name="community_info_monthly_active_users">ενεργοί χρήστες (μήνας)</string>
@ -87,9 +89,9 @@
<string name="inbox_section_messages">Μηνύματα</string>
<string name="inbox_section_replies">Απαντήσεις</string>
<string name="instance_detail_communities">Κοινότητες</string>
<string name="instance_detail_title">Παράδειγμα: %1$s</string>
<string name="instance_detail_title">Διακομιστής: %1$s</string>
<string name="lang">el</string>
<string name="login_field_instance_name">Όνομα παραδείγματος</string>
<string name="login_field_instance_name">Όνομα διακομιστή</string>
<string name="login_field_label_optional">(προαιρετικό)</string>
<string name="login_field_password">Κωδικός πρόσβασης</string>
<string name="login_field_token">Τοκεν 2FA TOTP</string>
@ -212,4 +214,6 @@
<string name="settings_upvote_color">Χρώμα ψήφου ανώτερου</string>
<string name="settings_hide_navigation_bar">Απόκρυψη της γραμμής πλοήγησης κατά την κύλιση
</string>
<string name="settings_zombie_mode_interval">Διάρκεια του διαστήματος της λειτουργίας ζόμπι
</string>
</resources>

View File

@ -5,6 +5,8 @@
<string name="action_clear_read">Esconder leídos</string>
<string name="action_create_post">Nueva publicación</string>
<string name="action_reply">Responder</string>
<string name="action_activate_zombie_mode">Activar modo zombie</string>
<string name="action_deactivate_zombie_mode">Desactivar modo zombie</string>
<string name="button_close">Cerrar</string>
<string name="button_confirm">Confirmar</string>
<string name="button_load">Cargar</string>
@ -212,4 +214,5 @@
<string name="settings_upvote_color">Color votos positivos</string>
<string name="settings_hide_navigation_bar">Ocultar la barra de navegación al desplazarse
</string>
<string name="settings_zombie_mode_interval">Duración del intervalo modo zombie</string>
</resources>

View File

@ -5,6 +5,8 @@
<string name="action_clear_read">Effacer les publications lues</string>
<string name="action_create_post">Créer une publication</string>
<string name="action_reply">Répondre</string>
<string name="action_activate_zombie_mode">Activer le mode zombie</string>
<string name="action_deactivate_zombie_mode">Désactiver le mode zombie</string>
<string name="button_close">Fermer</string>
<string name="button_confirm">Confirmer</string>
<string name="button_load">Charger</string>
@ -210,4 +212,5 @@
<string name="settings_upvote_color">Couleur votes positifs</string>
<string name="settings_hide_navigation_bar">Masquer la barre de navigation lors du défilement
</string>
<string name="settings_zombie_mode_interval">Durée de l\'intervalle du mode zombie</string>
</resources>

View File

@ -4,6 +4,8 @@
<string name="action_chat">Invia messaggio</string>
<string name="action_clear_read">Nascondi letti</string>
<string name="action_create_post">Crea post</string>
<string name="action_activate_zombie_mode">Attiva modalità zombie</string>
<string name="action_deactivate_zombie_mode">Disattiva modalità zombie</string>
<string name="action_reply">Rispondi</string>
<string name="button_close">Chiudi</string>
<string name="button_confirm">Conferma</string>
@ -210,4 +212,5 @@
<string name="settings_upvote_color">Colore voti positivi</string>
<string name="settings_hide_navigation_bar">Nascondi barra di navigazione durante lo scroll
</string>
<string name="settings_zombie_mode_interval">Durata intevallo modalità zombie</string>
</resources>

View File

@ -5,6 +5,8 @@
<string name="action_clear_read">Limpar lidas</string>
<string name="action_create_post">Criar publicação</string>
<string name="action_reply">Responder</string>
<string name="action_activate_zombie_mode">Ativar modo zumbi</string>
<string name="action_deactivate_zombie_mode">Desativar modo zumbi</string>
<string name="button_close">Fechar</string>
<string name="button_confirm">Confirmar</string>
<string name="button_load">Carregar</string>
@ -207,4 +209,5 @@
<string name="settings_ui_theme">Tema da interface</string>
<string name="settings_upvote_color">Cor votos positivos</string>
<string name="settings_hide_navigation_bar">Ocultar a barra de navegação ao rolar</string>
<string name="settings_zombie_mode_interval">Duração do intervalo do modo zumbi</string>
</resources>

View File

@ -5,6 +5,8 @@
<string name="action_clear_read">Ascunde citite</string>
<string name="action_create_post">Creează postare</string>
<string name="action_reply">Răspunde</string>
<string name="action_activate_zombie_mode">Activează modul zombie</string>
<string name="action_deactivate_zombie_mode">Dezactiează modul zombie</string>
<string name="button_close">Închide</string>
<string name="button_confirm">Confirmă</string>
<string name="button_load">Încărcă</string>
@ -208,4 +210,5 @@
<string name="settings_ui_theme">Tema UI</string>
<string name="settings_upvote_color">Culoare voturilor pozitive</string>
<string name="settings_hide_navigation_bar">Ascunde bara de navigare la derulare</string>
<string name="settings_zombie_mode_interval">Durată intervalului al modului zombie</string>
</resources>