Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:56:56 +01:00
parent 75dc487a7a
commit 39c29d175b
6317 changed files with 388324 additions and 2 deletions

View file

@ -0,0 +1,32 @@
plugins {
id(ThunderbirdPlugins.Library.androidCompose)
}
android {
namespace = "net.thunderbird.feature.navigation.drawer.siderail"
resourcePrefix = "navigation_drawer_siderail_"
}
dependencies {
api(projects.feature.navigation.drawer.api)
implementation(projects.core.ui.theme.api)
implementation(projects.core.ui.compose.designsystem)
implementation(projects.feature.account.avatar.impl)
implementation(projects.feature.mail.account.api)
implementation(projects.feature.mail.folder.api)
implementation(projects.core.android.account)
implementation(projects.legacy.mailstore)
implementation(projects.legacy.message)
implementation(projects.feature.search.implLegacy)
implementation(projects.legacy.ui.folder)
implementation(projects.core.featureflag)
testImplementation(projects.core.ui.compose.testing)
// Fakes
debugImplementation(projects.feature.account.fake)
testImplementation(projects.feature.account.fake)
}

View file

@ -0,0 +1,288 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import app.k9mail.core.ui.compose.designsystem.PreviewWithTheme
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import kotlin.collections.get
import kotlinx.collections.immutable.persistentListOf
import net.thunderbird.feature.navigation.drawer.api.NavigationDrawerExternalContract.DrawerConfig
import net.thunderbird.feature.navigation.drawer.siderail.ui.FakeData.DISPLAY_ACCOUNT
import net.thunderbird.feature.navigation.drawer.siderail.ui.FakeData.DISPLAY_FOLDER
import net.thunderbird.feature.navigation.drawer.siderail.ui.FakeData.UNIFIED_FOLDER
import net.thunderbird.feature.navigation.drawer.siderail.ui.FakeData.createAccountList
import net.thunderbird.feature.navigation.drawer.siderail.ui.FakeData.createDisplayFolderList
@Composable
@Preview(showBackground = true)
internal fun DrawerContentPreview() {
PreviewWithTheme {
DrawerContent(
state = DrawerContract.State(
accounts = persistentListOf(),
selectedAccountId = null,
folders = persistentListOf(),
),
onEvent = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun DrawerContentWithAccountPreview() {
PreviewWithTheme {
DrawerContent(
state = DrawerContract.State(
accounts = persistentListOf(DISPLAY_ACCOUNT),
selectedAccountId = DISPLAY_ACCOUNT.id,
folders = persistentListOf(),
),
onEvent = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun DrawerContentWithFoldersPreview() {
PreviewWithTheme {
DrawerContent(
state = DrawerContract.State(
accounts = persistentListOf(
DISPLAY_ACCOUNT,
),
selectedAccountId = null,
folders = persistentListOf(
UNIFIED_FOLDER,
DISPLAY_FOLDER,
),
),
onEvent = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun DrawerContentWithSelectedFolderPreview() {
PreviewWithTheme {
DrawerContent(
state = DrawerContract.State(
accounts = persistentListOf(
DISPLAY_ACCOUNT,
),
selectedAccountId = DISPLAY_ACCOUNT.id,
folders = persistentListOf(
UNIFIED_FOLDER,
DISPLAY_FOLDER,
),
selectedFolderId = DISPLAY_FOLDER.id,
),
onEvent = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun DrawerContentWithSelectedUnifiedFolderPreview() {
PreviewWithTheme {
DrawerContent(
state = DrawerContract.State(
accounts = persistentListOf(
DISPLAY_ACCOUNT,
),
selectedAccountId = DISPLAY_ACCOUNT.id,
folders = persistentListOf(
UNIFIED_FOLDER,
DISPLAY_FOLDER,
),
selectedFolderId = UNIFIED_FOLDER.id,
),
onEvent = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun DrawerContentSingleAccountPreview() {
val displayFolders = createDisplayFolderList(hasUnifiedFolder = false)
PreviewWithTheme {
DrawerContent(
state = DrawerContract.State(
accounts = persistentListOf(
DISPLAY_ACCOUNT,
),
selectedAccountId = DISPLAY_ACCOUNT.id,
folders = displayFolders,
selectedFolderId = displayFolders[0].id,
config = DrawerConfig(
showUnifiedFolders = false,
showStarredCount = false,
showAccountSelector = false,
),
),
onEvent = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun DrawerContentSingleAccountWithAccountSelectionPreview() {
val displayFolders = createDisplayFolderList(hasUnifiedFolder = false)
PreviewWithTheme {
DrawerContent(
state = DrawerContract.State(
accounts = persistentListOf(
DISPLAY_ACCOUNT,
),
selectedAccountId = DISPLAY_ACCOUNT.id,
folders = displayFolders,
selectedFolderId = displayFolders[0].id,
config = DrawerConfig(
showUnifiedFolders = false,
showStarredCount = false,
showAccountSelector = true,
),
),
onEvent = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun DrawerContentMultipleAccountsAccountPreview() {
val accountList = createAccountList()
val displayFolders = createDisplayFolderList(hasUnifiedFolder = true)
PreviewWithTheme {
DrawerContent(
state = DrawerContract.State(
accounts = accountList,
selectedAccountId = accountList[0].id,
folders = displayFolders,
selectedFolderId = UNIFIED_FOLDER.id,
config = DrawerConfig(
showUnifiedFolders = false,
showStarredCount = false,
showAccountSelector = false,
),
),
onEvent = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun DrawerContentMultipleAccountsWithAccountSelectionPreview() {
val accountList = createAccountList()
PreviewWithTheme {
DrawerContent(
state = DrawerContract.State(
accounts = accountList,
selectedAccountId = accountList[1].id,
folders = createDisplayFolderList(hasUnifiedFolder = true),
selectedFolderId = UNIFIED_FOLDER.id,
config = DrawerConfig(
showUnifiedFolders = false,
showStarredCount = false,
showAccountSelector = true,
),
),
onEvent = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun DrawerContentMultipleAccountsWithDifferentAccountSelectionPreview() {
val accountList = createAccountList()
PreviewWithTheme {
DrawerContent(
state = DrawerContract.State(
accounts = accountList,
selectedAccountId = accountList[2].id,
folders = createDisplayFolderList(hasUnifiedFolder = true),
selectedFolderId = UNIFIED_FOLDER.id,
config = DrawerConfig(
showUnifiedFolders = false,
showStarredCount = false,
showAccountSelector = true,
),
),
onEvent = {},
)
}
}
@Composable
@Preview(showBackground = true)
internal fun DrawerContentSmallScreenPreview() {
val accountList = createAccountList()
PreviewWithTheme {
Surface(
modifier = Modifier
.width(320.dp)
.height(480.dp),
) {
DrawerContent(
state = DrawerContract.State(
accounts = accountList,
selectedAccountId = accountList[2].id,
folders = createDisplayFolderList(hasUnifiedFolder = true),
selectedFolderId = UNIFIED_FOLDER.id,
config = DrawerConfig(
showUnifiedFolders = false,
showStarredCount = false,
showAccountSelector = true,
),
),
onEvent = {},
)
}
}
}
@Composable
@Preview(showBackground = true)
internal fun DrawerContentVerySmallScreenPreview() {
val accountList = createAccountList()
PreviewWithTheme {
Surface(
modifier = Modifier
.width(240.dp)
.height(320.dp),
) {
DrawerContent(
state = DrawerContract.State(
accounts = accountList,
selectedAccountId = accountList[2].id,
folders = createDisplayFolderList(hasUnifiedFolder = true),
selectedFolderId = UNIFIED_FOLDER.id,
config = DrawerConfig(
showUnifiedFolders = false,
showStarredCount = false,
showAccountSelector = true,
),
),
onEvent = {},
)
}
}
}

View file

@ -0,0 +1,158 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import net.thunderbird.account.fake.FakeAccountData.ACCOUNT_ID_RAW
import net.thunderbird.core.android.account.Identity
import net.thunderbird.core.android.account.LegacyAccount
import net.thunderbird.feature.mail.folder.api.Folder
import net.thunderbird.feature.mail.folder.api.FolderType
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayAccount
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayAccountFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayUnifiedFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayUnifiedFolderType
internal object FakeData {
const val DISPLAY_NAME = "Account Name"
const val EMAIL_ADDRESS = "test@example.com"
val ACCOUNT = LegacyAccount(
uuid = ACCOUNT_ID_RAW,
).apply {
identities = ArrayList()
val identity = Identity(
signatureUse = false,
signature = "",
description = "",
)
identities.add(identity)
name = DISPLAY_NAME
email = EMAIL_ADDRESS
}
val DISPLAY_ACCOUNT = DisplayAccount(
id = ACCOUNT_ID_RAW,
name = DISPLAY_NAME,
email = EMAIL_ADDRESS,
color = Color.Red.toArgb(),
unreadMessageCount = 0,
starredMessageCount = 0,
)
val FOLDER = Folder(
id = 1,
name = "Folder Name",
type = FolderType.REGULAR,
isLocalOnly = false,
)
val DISPLAY_FOLDER = DisplayAccountFolder(
accountId = ACCOUNT_ID_RAW,
folder = FOLDER,
isInTopGroup = false,
unreadMessageCount = 14,
starredMessageCount = 5,
)
val UNIFIED_FOLDER = DisplayUnifiedFolder(
id = "unified_inbox",
unifiedType = DisplayUnifiedFolderType.INBOX,
unreadMessageCount = 123,
starredMessageCount = 567,
)
fun createAccountList(): PersistentList<DisplayAccount> {
return persistentListOf(
DisplayAccount(
id = "account1",
name = "job@example.com",
email = "job@example.com",
color = Color.Green.toArgb(),
unreadMessageCount = 2,
starredMessageCount = 0,
),
DisplayAccount(
id = "account2",
name = "Jodie Doe",
email = "jodie@example.com",
color = Color.Red.toArgb(),
unreadMessageCount = 12,
starredMessageCount = 0,
),
DisplayAccount(
id = "account3",
name = "John Doe",
email = "john@example.com",
color = Color.Cyan.toArgb(),
unreadMessageCount = 0,
starredMessageCount = 0,
),
)
}
fun createDisplayFolderList(hasUnifiedFolder: Boolean): PersistentList<DisplayFolder> {
val folders = mutableListOf<DisplayFolder>()
if (hasUnifiedFolder) {
folders.add(UNIFIED_FOLDER)
}
folders.addAll(
listOf(
DISPLAY_FOLDER.copy(
folder = FOLDER.copy(id = 2, name = "Inbox", type = FolderType.INBOX),
unreadMessageCount = 12,
),
DISPLAY_FOLDER.copy(
folder = FOLDER.copy(id = 3, name = "Outbox", type = FolderType.OUTBOX),
unreadMessageCount = 0,
),
DISPLAY_FOLDER.copy(
folder = FOLDER.copy(id = 4, name = "Drafts", type = FolderType.DRAFTS),
unreadMessageCount = 0,
),
DISPLAY_FOLDER.copy(
folder = FOLDER.copy(id = 5, name = "Sent", type = FolderType.SENT),
unreadMessageCount = 0,
),
DISPLAY_FOLDER.copy(
folder = FOLDER.copy(id = 6, name = "Spam", type = FolderType.SPAM),
unreadMessageCount = 5,
),
DISPLAY_FOLDER.copy(
folder = FOLDER.copy(id = 7, name = "Trash", type = FolderType.TRASH),
unreadMessageCount = 0,
),
DISPLAY_FOLDER.copy(
folder = FOLDER.copy(id = 8, name = "Archive", type = FolderType.ARCHIVE),
unreadMessageCount = 0,
),
DISPLAY_FOLDER.copy(
folder = FOLDER.copy(id = 9, name = "Work", type = FolderType.REGULAR),
unreadMessageCount = 3,
),
DISPLAY_FOLDER.copy(
folder = FOLDER.copy(id = 10, name = "Personal", type = FolderType.REGULAR),
unreadMessageCount = 4,
),
DISPLAY_FOLDER.copy(
folder = FOLDER.copy(id = 11, name = "Important", type = FolderType.REGULAR),
unreadMessageCount = 0,
),
DISPLAY_FOLDER.copy(
folder = FOLDER.copy(id = 12, name = "Later", type = FolderType.REGULAR),
unreadMessageCount = 0,
),
),
)
return folders.toPersistentList()
}
}

View file

@ -0,0 +1,60 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.account
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import net.thunderbird.feature.navigation.drawer.siderail.ui.FakeData.DISPLAY_ACCOUNT
@Composable
@Preview(showBackground = true)
internal fun AccountAvatarPreview() {
PreviewWithThemes {
AccountAvatar(
account = DISPLAY_ACCOUNT,
onClick = {},
selected = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AccountAvatarWithUnreadCountPreview() {
PreviewWithThemes {
AccountAvatar(
account = DISPLAY_ACCOUNT.copy(
unreadMessageCount = 12,
),
onClick = {},
selected = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AccountAvatarWithUnreadCountMaxedPreview() {
PreviewWithThemes {
AccountAvatar(
account = DISPLAY_ACCOUNT.copy(
unreadMessageCount = 100,
),
onClick = {},
selected = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AccountAvatarSelectedPreview() {
PreviewWithThemes {
AccountAvatar(
account = DISPLAY_ACCOUNT.copy(
color = 0xFFFF0000.toInt(),
),
onClick = {},
selected = true,
)
}
}

View file

@ -0,0 +1,43 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.account
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
@Preview(showBackground = true)
internal fun AccountIndicatorPreview() {
PreviewWithThemes {
AccountIndicator(
accountColor = 0,
modifier = Modifier.height(MainTheme.spacings.double),
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AccountIndicatorPreviewWithYellowAccountColor() {
PreviewWithThemes {
AccountIndicator(
accountColor = Color.Yellow.toArgb(),
modifier = Modifier.height(MainTheme.spacings.double),
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AccountIndicatorPreviewWithGrayAccountColor() {
PreviewWithThemes {
AccountIndicator(
accountColor = Color.Gray.toArgb(),
modifier = Modifier.height(MainTheme.spacings.double),
)
}
}

View file

@ -0,0 +1,30 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.account
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import net.thunderbird.feature.navigation.drawer.siderail.ui.FakeData.DISPLAY_ACCOUNT
@Composable
@Preview(showBackground = true)
internal fun AccountListItemPreview() {
PreviewWithThemes {
AccountListItem(
account = DISPLAY_ACCOUNT,
onClick = { },
selected = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AccountListItemSelectedPreview() {
PreviewWithThemes {
AccountListItem(
account = DISPLAY_ACCOUNT,
onClick = { },
selected = true,
)
}
}

View file

@ -0,0 +1,39 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.account
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithTheme
import kotlinx.collections.immutable.persistentListOf
import net.thunderbird.feature.navigation.drawer.siderail.ui.FakeData.DISPLAY_ACCOUNT
@Composable
@Preview(showBackground = true)
internal fun AccountListPreview() {
PreviewWithTheme {
AccountList(
accounts = persistentListOf(
DISPLAY_ACCOUNT,
),
selectedAccount = null,
onAccountClick = { },
onSettingsClick = { },
onSyncAllAccountsClick = { },
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AccountListWithSelectedPreview() {
PreviewWithTheme {
AccountList(
accounts = persistentListOf(
DISPLAY_ACCOUNT,
),
selectedAccount = DISPLAY_ACCOUNT,
onAccountClick = { },
onSettingsClick = { },
onSyncAllAccountsClick = { },
)
}
}

View file

@ -0,0 +1,54 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.account
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import net.thunderbird.feature.navigation.drawer.siderail.ui.FakeData.DISPLAY_ACCOUNT
@Composable
@Preview(showBackground = true)
internal fun AccountViewPreview() {
PreviewWithThemes {
AccountView(
account = DISPLAY_ACCOUNT,
onClick = {},
showAvatar = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AccountViewWithColorPreview() {
PreviewWithThemes {
AccountView(
account = DISPLAY_ACCOUNT,
onClick = {},
showAvatar = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AccountViewWithLongDisplayName() {
PreviewWithThemes {
AccountView(
account = DISPLAY_ACCOUNT,
onClick = {},
showAvatar = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun AccountViewWithLongEmailPreview() {
PreviewWithThemes {
AccountView(
account = DISPLAY_ACCOUNT,
onClick = {},
showAvatar = false,
)
}
}

View file

@ -0,0 +1,101 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.folder
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@Preview(showBackground = true)
internal fun FolderListItemBadgePreview() {
PreviewWithThemes {
FolderListItemBadge(
unreadCount = 99,
starredCount = 0,
showStarredCount = true,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun FolderListItemBadgeWithStarredCountPreview() {
PreviewWithThemes {
FolderListItemBadge(
unreadCount = 99,
starredCount = 1,
showStarredCount = true,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun FolderListItemBadgeWithZeroUnreadCountPreview() {
PreviewWithThemes {
FolderListItemBadge(
unreadCount = 0,
starredCount = 1,
showStarredCount = true,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun FolderListItemBadgeWithZeroStarredCountPreview() {
PreviewWithThemes {
FolderListItemBadge(
unreadCount = 99,
starredCount = 0,
showStarredCount = true,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun FolderListItemBadgeWithZeroCountsPreview() {
PreviewWithThemes {
FolderListItemBadge(
unreadCount = 0,
starredCount = 0,
showStarredCount = true,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun FolderListItemBadgeWithoutStarredCountPreview() {
PreviewWithThemes {
FolderListItemBadge(
unreadCount = 99,
starredCount = 1,
showStarredCount = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun FolderListItemBadgeWith100CountsPreview() {
PreviewWithThemes {
FolderListItemBadge(
unreadCount = 100,
starredCount = 100,
showStarredCount = true,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun FolderListItemBadgeWith1000CountsPreview() {
PreviewWithThemes {
FolderListItemBadge(
unreadCount = 1000,
starredCount = 1000,
showStarredCount = true,
)
}
}

View file

@ -0,0 +1,98 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.folder
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.legacy.ui.folder.FolderNameFormatter
import net.thunderbird.feature.mail.folder.api.FolderType
import net.thunderbird.feature.navigation.drawer.siderail.ui.FakeData.DISPLAY_FOLDER
import net.thunderbird.feature.navigation.drawer.siderail.ui.FakeData.UNIFIED_FOLDER
@Composable
@Preview(showBackground = true)
internal fun FolderListItemPreview() {
PreviewWithThemes {
FolderListItem(
displayFolder = DISPLAY_FOLDER,
selected = false,
showStarredCount = false,
onClick = {},
folderNameFormatter = FolderNameFormatter(LocalContext.current.resources),
)
}
}
@Composable
@Preview(showBackground = true)
internal fun FolderListItemSelectedPreview() {
PreviewWithThemes {
FolderListItem(
displayFolder = DISPLAY_FOLDER,
selected = true,
showStarredCount = false,
onClick = {},
folderNameFormatter = FolderNameFormatter(LocalContext.current.resources),
)
}
}
@Composable
@Preview(showBackground = true)
internal fun FolderListItemWithStarredPreview() {
PreviewWithThemes {
FolderListItem(
displayFolder = DISPLAY_FOLDER,
selected = false,
showStarredCount = true,
onClick = {},
folderNameFormatter = FolderNameFormatter(LocalContext.current.resources),
)
}
}
@Composable
@Preview(showBackground = true)
internal fun FolderListItemWithStarredSelectedPreview() {
PreviewWithThemes {
FolderListItem(
displayFolder = DISPLAY_FOLDER,
selected = true,
showStarredCount = true,
onClick = {},
folderNameFormatter = FolderNameFormatter(LocalContext.current.resources),
)
}
}
@Composable
@Preview(showBackground = true)
internal fun FolderListItemWithInboxFolderPreview() {
PreviewWithThemes {
FolderListItem(
displayFolder = DISPLAY_FOLDER.copy(
folder = DISPLAY_FOLDER.folder.copy(
type = FolderType.INBOX,
),
),
selected = false,
showStarredCount = true,
onClick = {},
folderNameFormatter = FolderNameFormatter(LocalContext.current.resources),
)
}
}
@Composable
@Preview(showBackground = true)
internal fun FolderListItemWithUnifiedFolderPreview() {
PreviewWithThemes {
FolderListItem(
displayFolder = UNIFIED_FOLDER,
selected = false,
showStarredCount = false,
onClick = {},
folderNameFormatter = FolderNameFormatter(LocalContext.current.resources),
)
}
}

View file

@ -0,0 +1,54 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.folder
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithTheme
import kotlinx.collections.immutable.persistentListOf
import net.thunderbird.feature.navigation.drawer.siderail.ui.FakeData.DISPLAY_FOLDER
import net.thunderbird.feature.navigation.drawer.siderail.ui.FakeData.UNIFIED_FOLDER
@Composable
@Preview(showBackground = true)
internal fun FolderListPreview() {
PreviewWithTheme {
FolderList(
folders = persistentListOf(
DISPLAY_FOLDER,
),
selectedFolder = null,
onFolderClick = {},
showStarredCount = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun FolderListPreviewSelected() {
PreviewWithTheme {
FolderList(
folders = persistentListOf(
DISPLAY_FOLDER,
),
selectedFolder = DISPLAY_FOLDER,
onFolderClick = {},
showStarredCount = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun FolderListWithUnifiedFolderPreview() {
PreviewWithTheme {
FolderList(
folders = persistentListOf(
UNIFIED_FOLDER,
DISPLAY_FOLDER,
),
selectedFolder = DISPLAY_FOLDER,
onFolderClick = {},
showStarredCount = false,
)
}
}

View file

@ -0,0 +1,18 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.setting
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
@Composable
@Preview(showBackground = true)
internal fun SettingItemPreview() {
PreviewWithThemes {
SettingItem(
icon = Icons.Outlined.Settings,
label = "Setting",
onClick = {},
)
}
}

View file

@ -0,0 +1,18 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.setting
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
@Composable
@Preview(showBackground = true)
internal fun SettingListItemPreview() {
PreviewWithThemes {
SettingListItem(
label = "Settings",
onClick = {},
imageVector = Icons.Outlined.Settings,
)
}
}

View file

@ -0,0 +1,29 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.setting
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import app.k9mail.core.ui.compose.designsystem.PreviewWithTheme
@Composable
@Preview(showBackground = true)
internal fun SettingListPreview() {
PreviewWithTheme {
SettingList(
onAccountSelectorClick = {},
onManageFoldersClick = {},
showAccountSelector = false,
)
}
}
@Composable
@Preview(showBackground = true)
internal fun SettingListShowAccountSelectorPreview() {
PreviewWithTheme {
SettingList(
onAccountSelectorClick = {},
onManageFoldersClick = {},
showAccountSelector = true,
)
}
}

View file

@ -0,0 +1,74 @@
package net.thunderbird.feature.navigation.drawer.siderail
import net.thunderbird.feature.navigation.drawer.siderail.data.UnifiedFolderRepository
import net.thunderbird.feature.navigation.drawer.siderail.domain.DomainContract
import net.thunderbird.feature.navigation.drawer.siderail.domain.DomainContract.UseCase
import net.thunderbird.feature.navigation.drawer.siderail.domain.usecase.GetDisplayAccounts
import net.thunderbird.feature.navigation.drawer.siderail.domain.usecase.GetDisplayFoldersForAccount
import net.thunderbird.feature.navigation.drawer.siderail.domain.usecase.GetDrawerConfig
import net.thunderbird.feature.navigation.drawer.siderail.domain.usecase.SaveDrawerConfig
import net.thunderbird.feature.navigation.drawer.siderail.domain.usecase.SyncAccount
import net.thunderbird.feature.navigation.drawer.siderail.domain.usecase.SyncAllAccounts
import net.thunderbird.feature.navigation.drawer.siderail.ui.DrawerViewModel
import org.koin.core.module.Module
import org.koin.core.module.dsl.viewModel
import org.koin.dsl.module
val navigationSideRailDrawerModule: Module = module {
single<DomainContract.UnifiedFolderRepository> {
UnifiedFolderRepository(
messageCountsProvider = get(),
)
}
single<UseCase.GetDrawerConfig> {
GetDrawerConfig(
configLoader = get(),
)
}
single<UseCase.SaveDrawerConfig> {
SaveDrawerConfig(
drawerConfigWriter = get(),
)
}
single<UseCase.GetDisplayAccounts> {
GetDisplayAccounts(
accountManager = get(),
messageCountsProvider = get(),
messageListRepository = get(),
)
}
single<UseCase.GetDisplayFoldersForAccount> {
GetDisplayFoldersForAccount(
displayFolderRepository = get(),
unifiedFolderRepository = get(),
)
}
single<UseCase.SyncAccount> {
SyncAccount(
accountManager = get(),
messagingController = get(),
)
}
single<UseCase.SyncAllAccounts> {
SyncAllAccounts(
messagingController = get(),
)
}
viewModel {
DrawerViewModel(
getDrawerConfig = get(),
saveDrawerConfig = get(),
getDisplayAccounts = get(),
getDisplayFoldersForAccount = get(),
syncAccount = get(),
syncAllAccounts = get(),
)
}
}

View file

@ -0,0 +1,111 @@
package net.thunderbird.feature.navigation.drawer.siderail
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.ui.platform.ComposeView
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import net.thunderbird.core.ui.theme.api.FeatureThemeProvider
import net.thunderbird.feature.navigation.drawer.api.NavigationDrawer
import net.thunderbird.feature.navigation.drawer.api.R
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayUnifiedFolderType
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.createDisplayAccountFolderId
import net.thunderbird.feature.navigation.drawer.siderail.ui.DrawerView
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
internal data class FolderDrawerState(
val selectedAccountUuid: String? = null,
val selectedFolderId: String? = null,
)
class SideRailDrawer(
override val parent: AppCompatActivity,
private val openAccount: (accountId: String) -> Unit,
private val openFolder: (accountId: String, folderId: Long) -> Unit,
private val openUnifiedFolder: () -> Unit,
private val openManageFolders: () -> Unit,
private val openSettings: () -> Unit,
createDrawerListener: () -> DrawerLayout.DrawerListener,
) : NavigationDrawer, KoinComponent {
private val themeProvider: FeatureThemeProvider by inject()
private val drawer: DrawerLayout = parent.findViewById(R.id.navigation_drawer_layout)
private val drawerContent: ComposeView = parent.findViewById(R.id.navigation_drawer_content)
private val drawerState = MutableStateFlow(FolderDrawerState())
init {
drawer.addDrawerListener(createDrawerListener())
drawerContent.setContent {
themeProvider.WithTheme {
val state = drawerState.collectAsStateWithLifecycle()
DrawerView(
drawerState = state.value,
openAccount = openAccount,
openFolder = openFolder,
openUnifiedFolder = openUnifiedFolder,
openManageFolders = openManageFolders,
openSettings = openSettings,
closeDrawer = { close() },
)
}
}
}
override val isOpen: Boolean
get() = drawer.isOpen
override fun selectAccount(accountUuid: String) {
drawerState.update {
it.copy(selectedAccountUuid = accountUuid)
}
}
override fun selectFolder(accountUuid: String, folderId: Long) {
drawerState.update {
it.copy(
selectedAccountUuid = accountUuid,
selectedFolderId = createDisplayAccountFolderId(accountUuid, folderId),
)
}
}
override fun selectUnifiedInbox() {
drawerState.update {
it.copy(
selectedAccountUuid = "unified_account",
selectedFolderId = DisplayUnifiedFolderType.INBOX.id,
)
}
}
override fun deselect() {
drawerState.update {
it.copy(
selectedFolderId = null,
)
}
}
override fun open() {
drawer.openDrawer(GravityCompat.START)
}
override fun close() {
drawer.closeDrawer(GravityCompat.START)
}
override fun lock() {
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
}
override fun unlock() {
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
}
}

View file

@ -0,0 +1,44 @@
package net.thunderbird.feature.navigation.drawer.siderail.data
import app.k9mail.legacy.message.controller.MessageCountsProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import net.thunderbird.feature.navigation.drawer.siderail.domain.DomainContract
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayUnifiedFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayUnifiedFolderType
import net.thunderbird.feature.search.legacy.LocalMessageSearch
import net.thunderbird.feature.search.legacy.api.MessageSearchField
import net.thunderbird.feature.search.legacy.api.SearchAttribute
internal class UnifiedFolderRepository(
private val messageCountsProvider: MessageCountsProvider,
) : DomainContract.UnifiedFolderRepository {
override fun getDisplayUnifiedFolderFlow(unifiedFolderType: DisplayUnifiedFolderType): Flow<DisplayUnifiedFolder> {
return messageCountsProvider.getMessageCountsFlow(createUnifiedFolderSearch(unifiedFolderType)).map {
DisplayUnifiedFolder(
id = UNIFIED_INBOX_ID,
unifiedType = DisplayUnifiedFolderType.INBOX,
unreadMessageCount = it.unread,
starredMessageCount = it.starred,
)
}
}
private fun createUnifiedFolderSearch(unifiedFolderType: DisplayUnifiedFolderType): LocalMessageSearch {
return when (unifiedFolderType) {
DisplayUnifiedFolderType.INBOX -> return createUnifiedInboxSearch()
}
}
private fun createUnifiedInboxSearch(): LocalMessageSearch {
return LocalMessageSearch().apply {
id = UNIFIED_INBOX_ID
and(MessageSearchField.INTEGRATE, "1", SearchAttribute.EQUALS)
}
}
companion object {
const val UNIFIED_INBOX_ID = "unified_inbox"
}
}

View file

@ -0,0 +1,47 @@
package net.thunderbird.feature.navigation.drawer.siderail.domain
import kotlinx.coroutines.flow.Flow
import net.thunderbird.feature.navigation.drawer.api.NavigationDrawerExternalContract.DrawerConfig
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayAccount
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayUnifiedFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayUnifiedFolderType
internal interface DomainContract {
interface UseCase {
fun interface GetDrawerConfig {
operator fun invoke(): Flow<DrawerConfig>
}
fun interface SaveDrawerConfig {
operator fun invoke(drawerConfig: DrawerConfig): Flow<Unit>
}
fun interface GetDisplayAccounts {
operator fun invoke(): Flow<List<DisplayAccount>>
}
fun interface GetDisplayFoldersForAccount {
operator fun invoke(accountId: String, includeUnifiedFolders: Boolean): Flow<List<DisplayFolder>>
}
/**
* Synchronize the given account uuid.
*/
fun interface SyncAccount {
operator fun invoke(accountUuid: String): Flow<Result<Unit>>
}
/**
* Synchronize all accounts.
*/
fun interface SyncAllAccounts {
operator fun invoke(): Flow<Result<Unit>>
}
}
interface UnifiedFolderRepository {
fun getDisplayUnifiedFolderFlow(unifiedFolderType: DisplayUnifiedFolderType): Flow<DisplayUnifiedFolder>
}
}

View file

@ -0,0 +1,10 @@
package net.thunderbird.feature.navigation.drawer.siderail.domain.entity
internal data class DisplayAccount(
val id: String,
val name: String,
val email: String,
val color: Int,
val unreadMessageCount: Int,
val starredMessageCount: Int,
)

View file

@ -0,0 +1,17 @@
package net.thunderbird.feature.navigation.drawer.siderail.domain.entity
import net.thunderbird.feature.mail.folder.api.Folder
internal data class DisplayAccountFolder(
val accountId: String,
val folder: Folder,
val isInTopGroup: Boolean,
override val unreadMessageCount: Int,
override val starredMessageCount: Int,
) : DisplayFolder {
override val id: String = createDisplayAccountFolderId(accountId, folder.id)
}
fun createDisplayAccountFolderId(accountId: String, folderId: Long): String {
return "${accountId}_$folderId"
}

View file

@ -0,0 +1,7 @@
package net.thunderbird.feature.navigation.drawer.siderail.domain.entity
internal interface DisplayFolder {
val id: String
val unreadMessageCount: Int
val starredMessageCount: Int
}

View file

@ -0,0 +1,8 @@
package net.thunderbird.feature.navigation.drawer.siderail.domain.entity
internal data class DisplayUnifiedFolder(
override val id: String,
val unifiedType: DisplayUnifiedFolderType,
override val unreadMessageCount: Int,
override val starredMessageCount: Int,
) : DisplayFolder

View file

@ -0,0 +1,12 @@
package net.thunderbird.feature.navigation.drawer.siderail.domain.entity
/**
* Represents a unified folder in the drawer.
*
* The id is unique for each unified folder type.
*/
internal enum class DisplayUnifiedFolderType(
val id: String,
) {
INBOX("unified_inbox"),
}

View file

@ -0,0 +1,68 @@
package net.thunderbird.feature.navigation.drawer.siderail.domain.usecase
import app.k9mail.legacy.mailstore.MessageListChangedListener
import app.k9mail.legacy.mailstore.MessageListRepository
import app.k9mail.legacy.message.controller.MessageCounts
import app.k9mail.legacy.message.controller.MessageCountsProvider
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import net.thunderbird.core.android.account.AccountManager
import net.thunderbird.core.android.account.LegacyAccount
import net.thunderbird.feature.navigation.drawer.siderail.domain.DomainContract
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayAccount
internal class GetDisplayAccounts(
private val accountManager: AccountManager,
private val messageCountsProvider: MessageCountsProvider,
private val messageListRepository: MessageListRepository,
private val coroutineContext: CoroutineContext = Dispatchers.IO,
) : DomainContract.UseCase.GetDisplayAccounts {
@OptIn(ExperimentalCoroutinesApi::class)
override fun invoke(): Flow<List<DisplayAccount>> {
return accountManager.getAccountsFlow()
.flatMapLatest { accounts ->
val messageCountsFlows: List<Flow<MessageCounts>> = accounts.map { account ->
getMessageCountsFlow(account)
}
combine(messageCountsFlows) { messageCountsList ->
messageCountsList.mapIndexed { index, messageCounts ->
DisplayAccount(
id = accounts[index].uuid,
name = accounts[index].displayName,
email = accounts[index].email,
color = accounts[index].chipColor,
unreadMessageCount = messageCounts.unread,
starredMessageCount = messageCounts.starred,
)
}
}
}
}
private fun getMessageCountsFlow(account: LegacyAccount): Flow<MessageCounts> {
return callbackFlow {
send(messageCountsProvider.getMessageCounts(account))
val listener = MessageListChangedListener {
launch {
send(messageCountsProvider.getMessageCounts(account))
}
}
messageListRepository.addListener(account.uuid, listener)
awaitClose {
messageListRepository.removeListener(listener)
}
}.flowOn(coroutineContext)
}
}

View file

@ -0,0 +1,48 @@
package net.thunderbird.feature.navigation.drawer.siderail.domain.usecase
import app.k9mail.legacy.ui.folder.DisplayFolderRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import net.thunderbird.feature.navigation.drawer.siderail.domain.DomainContract
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayAccountFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayUnifiedFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayUnifiedFolderType
internal class GetDisplayFoldersForAccount(
private val displayFolderRepository: DisplayFolderRepository,
private val unifiedFolderRepository: DomainContract.UnifiedFolderRepository,
) : DomainContract.UseCase.GetDisplayFoldersForAccount {
override fun invoke(accountId: String, includeUnifiedFolders: Boolean): Flow<List<DisplayFolder>> {
val accountFoldersFlow: Flow<List<DisplayFolder>> =
displayFolderRepository.getDisplayFoldersFlow(accountId).map { displayFolders ->
displayFolders.map { displayFolder ->
DisplayAccountFolder(
accountId = accountId,
folder = displayFolder.folder,
isInTopGroup = displayFolder.isInTopGroup,
unreadMessageCount = displayFolder.unreadMessageCount,
starredMessageCount = displayFolder.starredMessageCount,
)
}
}
val unifiedFoldersFlow: Flow<List<DisplayFolder>> = if (includeUnifiedFolders) {
unifiedFolderRepository.getDisplayUnifiedFolderFlow(DisplayUnifiedFolderType.INBOX)
.map { displayUnifiedFolder ->
listOf(displayUnifiedFolder)
}
} else {
flowOf(emptyList<DisplayUnifiedFolder>())
}
return combine(
accountFoldersFlow,
unifiedFoldersFlow,
) { accountFolders, unifiedFolders ->
unifiedFolders + accountFolders
}
}
}

View file

@ -0,0 +1,14 @@
package net.thunderbird.feature.navigation.drawer.siderail.domain.usecase
import kotlinx.coroutines.flow.Flow
import net.thunderbird.feature.navigation.drawer.api.NavigationDrawerExternalContract.DrawerConfig
import net.thunderbird.feature.navigation.drawer.api.NavigationDrawerExternalContract.DrawerConfigLoader
import net.thunderbird.feature.navigation.drawer.siderail.domain.DomainContract
internal class GetDrawerConfig(
private val configLoader: DrawerConfigLoader,
) : DomainContract.UseCase.GetDrawerConfig {
override operator fun invoke(): Flow<DrawerConfig> {
return configLoader.loadDrawerConfigFlow()
}
}

View file

@ -0,0 +1,17 @@
package net.thunderbird.feature.navigation.drawer.siderail.domain.usecase
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import net.thunderbird.feature.navigation.drawer.api.NavigationDrawerExternalContract
import net.thunderbird.feature.navigation.drawer.api.NavigationDrawerExternalContract.DrawerConfigWriter
import net.thunderbird.feature.navigation.drawer.siderail.domain.DomainContract
internal class SaveDrawerConfig(
private val drawerConfigWriter: DrawerConfigWriter,
) : DomainContract.UseCase.SaveDrawerConfig {
override fun invoke(drawerConfig: NavigationDrawerExternalContract.DrawerConfig): Flow<Unit> {
return flow {
emit(drawerConfigWriter.writeDrawerConfig(drawerConfig))
}
}
}

View file

@ -0,0 +1,41 @@
package net.thunderbird.feature.navigation.drawer.siderail.domain.usecase
import android.content.Context
import app.k9mail.legacy.message.controller.MessagingControllerMailChecker
import app.k9mail.legacy.message.controller.SimpleMessagingListener
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOn
import net.thunderbird.core.android.account.AccountManager
import net.thunderbird.core.android.account.LegacyAccount
import net.thunderbird.feature.navigation.drawer.siderail.domain.DomainContract
internal class SyncAccount(
private val accountManager: AccountManager,
private val messagingController: MessagingControllerMailChecker,
private val coroutineContext: CoroutineContext = Dispatchers.IO,
) : DomainContract.UseCase.SyncAccount {
override fun invoke(accountUuid: String): Flow<Result<Unit>> = callbackFlow {
val listener = object : SimpleMessagingListener() {
override fun checkMailFinished(context: Context?, account: LegacyAccount?) {
trySend(Result.success(Unit))
close()
}
}
val account = accountManager.getAccount(accountUuid)
messagingController.checkMail(
account = account,
ignoreLastCheckedTime = true,
useManualWakeLock = true,
notify = true,
listener = listener,
)
awaitClose()
}.flowOn(coroutineContext)
}

View file

@ -0,0 +1,37 @@
package net.thunderbird.feature.navigation.drawer.siderail.domain.usecase
import android.content.Context
import app.k9mail.legacy.message.controller.MessagingControllerMailChecker
import app.k9mail.legacy.message.controller.SimpleMessagingListener
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOn
import net.thunderbird.core.android.account.LegacyAccount
import net.thunderbird.feature.navigation.drawer.siderail.domain.DomainContract
class SyncAllAccounts(
private val messagingController: MessagingControllerMailChecker,
private val coroutineContext: CoroutineContext = Dispatchers.IO,
) : DomainContract.UseCase.SyncAllAccounts {
override fun invoke(): Flow<Result<Unit>> = callbackFlow {
val listener = object : SimpleMessagingListener() {
override fun checkMailFinished(context: Context?, account: LegacyAccount?) {
trySend(Result.success(Unit))
close()
}
}
messagingController.checkMail(
account = null,
ignoreLastCheckedTime = true,
useManualWakeLock = true,
notify = true,
listener = listener,
)
awaitClose()
}.flowOn(coroutineContext)
}

View file

@ -0,0 +1,110 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import app.k9mail.core.ui.compose.designsystem.atom.DividerHorizontal
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import net.thunderbird.core.ui.compose.common.modifier.testTagAsResourceId
import net.thunderbird.feature.navigation.drawer.siderail.ui.account.AccountList
import net.thunderbird.feature.navigation.drawer.siderail.ui.account.AccountView
import net.thunderbird.feature.navigation.drawer.siderail.ui.folder.FolderList
import net.thunderbird.feature.navigation.drawer.siderail.ui.setting.SettingList
// As long as we use DrawerLayout, we don't have to worry about screens narrower than DRAWER_WIDTH. DrawerLayout will
// automatically limit the width of the content view so there's still room for a scrim with minimum tap width.
private val DRAWER_WIDTH = 360.dp
@Composable
internal fun DrawerContent(
state: DrawerContract.State,
onEvent: (DrawerContract.Event) -> Unit,
modifier: Modifier = Modifier,
) {
val additionalWidth = getAdditionalWidth()
Surface(
modifier = modifier
.windowInsetsPadding(WindowInsets.statusBars)
.width(DRAWER_WIDTH + additionalWidth)
.fillMaxHeight()
.testTagAsResourceId("DrawerContent"),
) {
val selectedAccount = state.accounts.firstOrNull { it.id == state.selectedAccountId }
Column {
selectedAccount?.let {
AccountView(
account = selectedAccount,
onClick = { onEvent(DrawerContract.Event.OnAccountViewClick(selectedAccount)) },
showAvatar = state.config.showAccountSelector,
)
DividerHorizontal()
}
Row {
AnimatedVisibility(
visible = state.config.showAccountSelector,
) {
AccountList(
accounts = state.accounts,
selectedAccount = selectedAccount,
onAccountClick = { onEvent(DrawerContract.Event.OnAccountClick(it)) },
onSyncAllAccountsClick = { onEvent(DrawerContract.Event.OnSyncAllAccounts) },
onSettingsClick = { onEvent(DrawerContract.Event.OnSettingsClick) },
)
}
Column(
modifier = Modifier
.weight(1f)
.fillMaxSize(),
) {
FolderList(
folders = state.folders,
selectedFolder = state.folders.firstOrNull { it.id == state.selectedFolderId },
onFolderClick = { folder ->
onEvent(DrawerContract.Event.OnFolderClick(folder))
},
showStarredCount = state.config.showStarredCount,
modifier = Modifier.weight(1f),
)
DividerHorizontal()
SettingList(
onAccountSelectorClick = { onEvent(DrawerContract.Event.OnAccountSelectorClick) },
onManageFoldersClick = { onEvent(DrawerContract.Event.OnManageFoldersClick) },
showAccountSelector = state.config.showAccountSelector,
)
}
}
}
}
}
@Composable
fun getAdditionalWidth(): Dp {
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
return if (isRtl) {
WindowInsets.displayCutout.getRight(density = density, layoutDirection = layoutDirection)
} else {
WindowInsets.displayCutout.getLeft(density = density, layoutDirection = layoutDirection)
}.pxToDp()
}
@Composable
fun Int.pxToDp() = with(LocalDensity.current) { this@pxToDp.toDp() }

View file

@ -0,0 +1,50 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui
import androidx.compose.runtime.Stable
import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import net.thunderbird.feature.navigation.drawer.api.NavigationDrawerExternalContract.DrawerConfig
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayAccount
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayFolder
internal interface DrawerContract {
interface ViewModel : UnidirectionalViewModel<State, Event, Effect>
@Stable
data class State(
val config: DrawerConfig = DrawerConfig(
showUnifiedFolders = false,
showStarredCount = false,
showAccountSelector = true,
),
val accounts: ImmutableList<DisplayAccount> = persistentListOf(),
val selectedAccountId: String? = null,
val folders: ImmutableList<DisplayFolder> = persistentListOf(),
val selectedFolderId: String? = null,
val isLoading: Boolean = false,
)
sealed interface Event {
data class SelectAccount(val accountId: String?) : Event
data class SelectFolder(val folderId: String?) : Event
data class OnAccountClick(val account: DisplayAccount) : Event
data class OnAccountViewClick(val account: DisplayAccount) : Event
data class OnFolderClick(val folder: DisplayFolder) : Event
data object OnAccountSelectorClick : Event
data object OnManageFoldersClick : Event
data object OnSettingsClick : Event
data object OnSyncAccount : Event
data object OnSyncAllAccounts : Event
}
sealed interface Effect {
data class OpenAccount(val accountId: String) : Effect
data class OpenFolder(val accountId: String, val folderId: Long) : Effect
data object OpenUnifiedFolder : Effect
data object OpenManageFolders : Effect
data object OpenSettings : Effect
data object CloseDrawer : Effect
}
}

View file

@ -0,0 +1,53 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import app.k9mail.core.ui.compose.common.mvi.observe
import app.k9mail.core.ui.compose.designsystem.molecule.PullToRefreshBox
import net.thunderbird.feature.navigation.drawer.siderail.FolderDrawerState
import org.koin.androidx.compose.koinViewModel
@Composable
internal fun DrawerView(
drawerState: FolderDrawerState,
openAccount: (accountId: String) -> Unit,
openFolder: (accountId: String, folderId: Long) -> Unit,
openUnifiedFolder: () -> Unit,
openManageFolders: () -> Unit,
openSettings: () -> Unit,
closeDrawer: () -> Unit,
viewModel: DrawerContract.ViewModel = koinViewModel<DrawerViewModel>(),
) {
val (state, dispatch) = viewModel.observe { effect ->
when (effect) {
is DrawerContract.Effect.OpenAccount -> openAccount(effect.accountId)
is DrawerContract.Effect.OpenFolder -> openFolder(
effect.accountId,
effect.folderId,
)
DrawerContract.Effect.OpenUnifiedFolder -> openUnifiedFolder()
is DrawerContract.Effect.OpenManageFolders -> openManageFolders()
is DrawerContract.Effect.OpenSettings -> openSettings()
DrawerContract.Effect.CloseDrawer -> closeDrawer()
}
}
LaunchedEffect(drawerState.selectedAccountUuid) {
dispatch(DrawerContract.Event.SelectAccount(drawerState.selectedAccountUuid))
}
LaunchedEffect(drawerState.selectedFolderId) {
dispatch(DrawerContract.Event.SelectFolder(drawerState.selectedFolderId))
}
PullToRefreshBox(
isRefreshing = state.value.isLoading,
onRefresh = { dispatch(DrawerContract.Event.OnSyncAccount) },
) {
DrawerContent(
state = state.value,
onEvent = { dispatch(it) },
)
}
}

View file

@ -0,0 +1,215 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui
import androidx.lifecycle.viewModelScope
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import net.thunderbird.feature.navigation.drawer.siderail.domain.DomainContract
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayAccount
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayAccountFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayUnifiedFolder
@Suppress("MagicNumber", "TooManyFunctions")
internal class DrawerViewModel(
private val getDrawerConfig: DomainContract.UseCase.GetDrawerConfig,
private val saveDrawerConfig: DomainContract.UseCase.SaveDrawerConfig,
private val getDisplayAccounts: DomainContract.UseCase.GetDisplayAccounts,
private val getDisplayFoldersForAccount: DomainContract.UseCase.GetDisplayFoldersForAccount,
private val syncAccount: DomainContract.UseCase.SyncAccount,
private val syncAllAccounts: DomainContract.UseCase.SyncAllAccounts,
initialState: DrawerContract.State = DrawerContract.State(),
) : BaseViewModel<DrawerContract.State, DrawerContract.Event, DrawerContract.Effect>(
initialState = initialState,
),
DrawerContract.ViewModel {
init {
viewModelScope.launch {
getDrawerConfig().collectLatest { config ->
updateState {
it.copy(config = config)
}
}
}
viewModelScope.launch {
loadAccounts()
}
viewModelScope.launch {
loadFolders()
}
}
private suspend fun loadAccounts() {
getDisplayAccounts().collectLatest { accounts ->
updateAccounts(accounts)
}
}
private fun updateAccounts(accounts: List<DisplayAccount>) {
val selectedAccount = accounts.find { it.id == state.value.selectedAccountId }
?: accounts.firstOrNull()
updateState {
it.copy(
accounts = accounts.toImmutableList(),
selectedAccountId = selectedAccount?.id,
)
}
}
@OptIn(ExperimentalCoroutinesApi::class)
private suspend fun loadFolders() {
state.map {
it.selectedAccountId?.let { accountId ->
Pair(accountId, it.config.showUnifiedFolders)
}
}.filterNotNull()
.distinctUntilChanged()
.flatMapLatest { (accountId, showUnifiedInbox) ->
getDisplayFoldersForAccount(accountId, showUnifiedInbox)
}.collect { folders ->
updateFolders(folders)
}
}
private fun updateFolders(displayFolders: List<DisplayFolder>) {
val selectedFolder = displayFolders.find {
it.id == state.value.selectedFolderId
} ?: displayFolders.firstOrNull()
updateState {
it.copy(
folders = displayFolders.toImmutableList(),
selectedFolderId = selectedFolder?.id,
)
}
}
override fun event(event: DrawerContract.Event) {
when (event) {
is DrawerContract.Event.SelectAccount -> selectAccount(event.accountId)
is DrawerContract.Event.SelectFolder -> selectFolder(event.folderId)
is DrawerContract.Event.OnAccountClick -> openAccount(event.account)
is DrawerContract.Event.OnFolderClick -> openFolder(event.folder)
is DrawerContract.Event.OnAccountViewClick -> {
openAccount(
state.value.accounts.nextOrFirst(event.account),
)
}
DrawerContract.Event.OnAccountSelectorClick -> {
saveDrawerConfig(
state.value.config.copy(showAccountSelector = state.value.config.showAccountSelector.not()),
).launchIn(viewModelScope)
}
DrawerContract.Event.OnManageFoldersClick -> emitEffect(DrawerContract.Effect.OpenManageFolders)
DrawerContract.Event.OnSettingsClick -> emitEffect(DrawerContract.Effect.OpenSettings)
DrawerContract.Event.OnSyncAccount -> onSyncAccount()
DrawerContract.Event.OnSyncAllAccounts -> onSyncAllAccounts()
}
}
private fun selectAccount(accountId: String?) {
updateState {
it.copy(
selectedAccountId = accountId,
)
}
}
private fun selectFolder(folderId: String?) {
updateState {
it.copy(
selectedFolderId = folderId,
)
}
}
private fun openAccount(account: DisplayAccount?) {
if (account != null) {
emitEffect(DrawerContract.Effect.OpenAccount(account.id))
}
}
private fun ImmutableList<DisplayAccount>.nextOrFirst(account: DisplayAccount): DisplayAccount? {
val index = indexOf(account)
return if (index == -1) {
null
} else if (index == size - 1) {
get(0)
} else {
get(index + 1)
}
}
private fun openFolder(folder: DisplayFolder) {
if (folder is DisplayAccountFolder) {
emitEffect(
DrawerContract.Effect.OpenFolder(
accountId = folder.accountId,
folderId = folder.folder.id,
),
)
} else if (folder is DisplayUnifiedFolder) {
emitEffect(DrawerContract.Effect.OpenUnifiedFolder)
}
viewModelScope.launch {
delay(DRAWER_CLOSE_DELAY)
emitEffect(DrawerContract.Effect.CloseDrawer)
}
}
private fun onSyncAccount() {
if (state.value.isLoading || state.value.selectedAccountId == null) return
viewModelScope.launch {
updateState {
it.copy(isLoading = true)
}
state.value.selectedAccountId?.let { syncAccount(it).collect() }
updateState {
it.copy(isLoading = false)
}
}
}
private fun onSyncAllAccounts() {
if (state.value.isLoading) return
viewModelScope.launch {
updateState {
it.copy(isLoading = true)
}
syncAllAccounts().collect()
updateState {
it.copy(isLoading = false)
}
}
}
}
/**
* Delay before closing the drawer to avoid the drawer being closed immediately and give time
* for the ripple effect to finish.
*/
private const val DRAWER_CLOSE_DELAY = 250L

View file

@ -0,0 +1,74 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.account
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import app.k9mail.core.ui.compose.designsystem.atom.text.TextLabelSmall
import app.k9mail.core.ui.compose.theme2.ColorRoles
import app.k9mail.core.ui.compose.theme2.toColorRoles
import net.thunderbird.feature.account.avatar.ui.Avatar
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayAccount
import net.thunderbird.feature.navigation.drawer.siderail.ui.common.labelForCount
@Composable
internal fun AccountAvatar(
account: DisplayAccount,
selected: Boolean,
modifier: Modifier = Modifier,
onClick: ((DisplayAccount) -> Unit)? = null,
) {
val context = LocalContext.current
val accountColor = calculateAccountColor(account.color)
val accountColorRoles = accountColor.toColorRoles(context)
Box(
modifier = modifier,
contentAlignment = Alignment.BottomEnd,
) {
Avatar(
color = accountColor,
name = account.name,
onClick = onClick?.let { { onClick(account) } },
selected = selected,
)
UnreadBadge(
unreadCount = account.unreadMessageCount,
accountColorRoles = accountColorRoles,
)
}
}
@Composable
private fun UnreadBadge(
unreadCount: Int,
accountColorRoles: ColorRoles,
modifier: Modifier = Modifier,
) {
if (unreadCount > 0) {
val resources = LocalContext.current.resources
Surface(
color = accountColorRoles.accent,
shape = CircleShape,
modifier = modifier,
) {
TextLabelSmall(
text = labelForCount(
count = unreadCount,
resources = resources,
),
color = accountColorRoles.onAccent,
modifier = Modifier.padding(
horizontal = 3.dp,
vertical = 2.dp,
),
)
}
}
}

View file

@ -0,0 +1,24 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.account
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
internal fun AccountIndicator(
accountColor: Int,
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier
.width(MainTheme.spacings.half)
.defaultMinSize(
minHeight = MainTheme.spacings.default,
),
color = calculateAccountColor(accountColor),
shape = MainTheme.shapes.medium,
) {}
}

View file

@ -0,0 +1,82 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.account
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
import app.k9mail.core.ui.compose.theme2.MainTheme
import kotlinx.collections.immutable.ImmutableList
import net.thunderbird.feature.navigation.drawer.siderail.R
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayAccount
import net.thunderbird.feature.navigation.drawer.siderail.ui.setting.SettingItem
@Composable
internal fun AccountList(
accounts: ImmutableList<DisplayAccount>,
selectedAccount: DisplayAccount?,
onAccountClick: (DisplayAccount) -> Unit,
onSyncAllAccountsClick: () -> Unit,
onSettingsClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier,
color = MainTheme.colors.surfaceContainer,
) {
val horizontalInsetPadding = getDisplayCutOutHorizontalInsetPadding()
Column(
modifier = Modifier
.fillMaxHeight()
.windowInsetsPadding(WindowInsets.navigationBars)
.windowInsetsPadding(horizontalInsetPadding)
.width(MainTheme.sizes.large),
) {
LazyColumn(
modifier = Modifier.weight(1f),
contentPadding = PaddingValues(vertical = MainTheme.spacings.default),
) {
items(
items = accounts,
key = { account -> account.id },
) { account ->
AccountListItem(
account = account,
onClick = { onAccountClick(account) },
selected = selectedAccount == account,
)
}
}
Column(
modifier = Modifier.padding(vertical = MainTheme.spacings.oneHalf),
) {
SettingItem(
icon = Icons.Outlined.Sync,
label = stringResource(id = R.string.navigation_drawer_siderail_action_sync_all_accounts),
onClick = onSyncAllAccountsClick,
)
// Hack to compensate the column placement at an uneven coordinate, caused by the 1.dp divider.
Spacer(modifier = Modifier.height(7.dp))
SettingItem(
icon = Icons.Outlined.Settings,
label = stringResource(id = R.string.navigation_drawer_siderail_action_settings),
onClick = onSettingsClick,
)
}
}
}
}

View file

@ -0,0 +1,30 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.account
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import app.k9mail.core.ui.compose.theme2.MainTheme
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayAccount
@Composable
internal fun AccountListItem(
account: DisplayAccount,
onClick: (DisplayAccount) -> Unit,
selected: Boolean,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier.width(MainTheme.sizes.large)
.padding(vertical = MainTheme.spacings.half),
contentAlignment = Alignment.Center,
) {
AccountAvatar(
account = account,
onClick = onClick,
selected = selected,
)
}
}

View file

@ -0,0 +1,108 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.account
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.LayoutDirection
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyLarge
import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyMedium
import app.k9mail.core.ui.compose.theme2.MainTheme
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayAccount
@Suppress("LongMethod")
@Composable
internal fun AccountView(
account: DisplayAccount,
onClick: () -> Unit,
showAvatar: Boolean,
modifier: Modifier = Modifier,
) {
Row(
modifier = Modifier.fillMaxWidth()
.height(intrinsicSize = IntrinsicSize.Max),
verticalAlignment = Alignment.CenterVertically,
) {
AnimatedVisibility(visible = showAvatar) {
Surface(
color = MainTheme.colors.surfaceContainer,
modifier = Modifier.fillMaxHeight(),
) {
val horizontalInsetPadding = getDisplayCutOutHorizontalInsetPadding()
Box(
modifier = Modifier
.windowInsetsPadding(horizontalInsetPadding)
.width(MainTheme.sizes.large),
contentAlignment = Alignment.Center,
) {
AccountAvatar(
account = account,
onClick = null,
selected = false,
)
}
}
}
Row(
modifier = modifier
.clickable(onClick = onClick)
.height(intrinsicSize = IntrinsicSize.Max)
.fillMaxWidth()
.defaultMinSize(minHeight = MainTheme.sizes.large)
.padding(
top = MainTheme.spacings.double,
start = MainTheme.spacings.double,
end = MainTheme.spacings.triple,
bottom = MainTheme.spacings.double,
),
verticalAlignment = Alignment.CenterVertically,
) {
AccountIndicator(
accountColor = account.color,
modifier = Modifier
.fillMaxHeight()
.padding(end = MainTheme.spacings.oneHalf),
)
Column(
verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.half),
) {
TextBodyLarge(
text = account.name,
color = MainTheme.colors.onSurface,
)
if (account.name != account.email) {
TextBodyMedium(
text = account.email,
color = MainTheme.colors.onSurfaceVariant,
)
}
}
}
}
}
@Composable
fun getDisplayCutOutHorizontalInsetPadding(): WindowInsets {
val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
return WindowInsets.displayCutout.only(if (isRtl) WindowInsetsSides.Right else WindowInsetsSides.Left)
}

View file

@ -0,0 +1,15 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.account
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import app.k9mail.core.ui.compose.theme2.MainTheme
import app.k9mail.core.ui.compose.theme2.toHarmonizedColor
@Composable
internal fun calculateAccountColor(accountColor: Int): Color {
return if (accountColor == 0) {
MainTheme.colors.primary
} else {
Color(accountColor).toHarmonizedColor(MainTheme.colors.surface)
}
}

View file

@ -0,0 +1,22 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.common
import android.content.res.Resources
import net.thunderbird.feature.navigation.drawer.siderail.R
@Suppress("MagicNumber")
internal fun labelForCount(
count: Int,
resources: Resources,
) = when {
count in 1..99 -> "$count"
count in 100..1000 -> resources.getString(
R.string.navigation_drawer_siderail_folder_item_badge_count_greater_than_99,
)
count > 1000 -> resources.getString(
R.string.navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000,
)
else -> ""
}

View file

@ -0,0 +1,61 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.folder
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import app.k9mail.core.ui.compose.designsystem.atom.DividerHorizontal
import app.k9mail.core.ui.compose.theme2.MainTheme
import app.k9mail.legacy.ui.folder.FolderNameFormatter
import kotlinx.collections.immutable.ImmutableList
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayUnifiedFolder
@Composable
internal fun FolderList(
folders: ImmutableList<DisplayFolder>,
selectedFolder: DisplayFolder?,
onFolderClick: (DisplayFolder) -> Unit,
showStarredCount: Boolean,
modifier: Modifier = Modifier,
) {
val resources = LocalContext.current.resources
val folderNameFormatter = remember { FolderNameFormatter(resources) }
val listState = rememberLazyListState()
LazyColumn(
state = listState,
modifier = modifier
.fillMaxWidth(),
contentPadding = PaddingValues(vertical = MainTheme.spacings.default),
) {
items(
items = folders,
key = { it.id },
) { folder ->
FolderListItem(
displayFolder = folder,
selected = folder == selectedFolder,
showStarredCount = showStarredCount,
onClick = onFolderClick,
folderNameFormatter = folderNameFormatter,
)
if (folder is DisplayUnifiedFolder) {
DividerHorizontal(
modifier = Modifier
.fillMaxWidth()
.padding(
vertical = MainTheme.spacings.default,
horizontal = MainTheme.spacings.triple,
),
)
}
}
}
}

View file

@ -0,0 +1,91 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.folder
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icon
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
import app.k9mail.core.ui.compose.designsystem.organism.drawer.NavigationDrawerItem
import app.k9mail.legacy.ui.folder.FolderNameFormatter
import net.thunderbird.feature.mail.folder.api.FolderType
import net.thunderbird.feature.navigation.drawer.siderail.R
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayAccountFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayUnifiedFolder
import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayUnifiedFolderType
@Composable
internal fun FolderListItem(
displayFolder: DisplayFolder,
selected: Boolean,
onClick: (DisplayFolder) -> Unit,
showStarredCount: Boolean,
folderNameFormatter: FolderNameFormatter,
modifier: Modifier = Modifier,
) {
NavigationDrawerItem(
label = mapFolderName(displayFolder, folderNameFormatter),
selected = selected,
onClick = { onClick(displayFolder) },
modifier = modifier,
icon = {
Icon(
imageVector = mapFolderIcon(displayFolder),
)
},
badge = {
FolderListItemBadge(
unreadCount = displayFolder.unreadMessageCount,
starredCount = displayFolder.starredMessageCount,
showStarredCount = showStarredCount,
)
},
)
}
@Composable
private fun mapFolderName(
displayFolder: DisplayFolder,
folderNameFormatter: FolderNameFormatter,
): String {
return when (displayFolder) {
is DisplayAccountFolder -> folderNameFormatter.displayName(displayFolder.folder)
is DisplayUnifiedFolder -> mapUnifiedFolderName(displayFolder)
else -> throw IllegalArgumentException("Unknown display folder: $displayFolder")
}
}
@Composable
private fun mapUnifiedFolderName(folder: DisplayUnifiedFolder): String {
return when (folder.unifiedType) {
DisplayUnifiedFolderType.INBOX -> stringResource(R.string.navigation_drawer_siderail_unified_inbox_title)
}
}
private fun mapFolderIcon(folder: DisplayFolder): ImageVector {
return when (folder) {
is DisplayAccountFolder -> mapDisplayAccountFolderIcon(folder)
is DisplayUnifiedFolder -> mapDisplayUnifiedFolderIcon(folder)
else -> throw IllegalArgumentException("Unknown display folder type: $folder")
}
}
private fun mapDisplayAccountFolderIcon(folder: DisplayAccountFolder): ImageVector {
return when (folder.folder.type) {
FolderType.INBOX -> Icons.Outlined.Inbox
FolderType.OUTBOX -> Icons.Outlined.Outbox
FolderType.SENT -> Icons.Outlined.Send
FolderType.TRASH -> Icons.Outlined.Delete
FolderType.DRAFTS -> Icons.Outlined.Drafts
FolderType.ARCHIVE -> Icons.Outlined.Archive
FolderType.SPAM -> Icons.Outlined.Report
FolderType.REGULAR -> Icons.Outlined.Folder
}
}
private fun mapDisplayUnifiedFolderIcon(folder: DisplayUnifiedFolder): ImageVector {
when (folder.unifiedType) {
DisplayUnifiedFolderType.INBOX -> return Icons.Outlined.AllInbox
}
}

View file

@ -0,0 +1,89 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.folder
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
import app.k9mail.core.ui.compose.designsystem.organism.drawer.NavigationDrawerItemBadge
import app.k9mail.core.ui.compose.theme2.MainTheme
import net.thunderbird.core.ui.compose.designsystem.atom.icon.filled.Star
import net.thunderbird.feature.navigation.drawer.siderail.ui.common.labelForCount
@Composable
internal fun FolderListItemBadge(
unreadCount: Int,
starredCount: Int,
showStarredCount: Boolean,
modifier: Modifier = Modifier,
) {
if (showStarredCount) {
FolderCountAndStarredBadge(
unreadCount = unreadCount,
starredCount = starredCount,
modifier = modifier,
)
} else {
FolderCountBadge(
unreadCount = unreadCount,
modifier = modifier,
)
}
}
@Composable
private fun FolderCountBadge(
unreadCount: Int,
modifier: Modifier = Modifier,
) {
if (unreadCount > 0) {
val resources = LocalContext.current.resources
NavigationDrawerItemBadge(
label = labelForCount(
count = unreadCount,
resources = resources,
),
modifier = modifier,
)
}
}
@Composable
private fun FolderCountAndStarredBadge(
unreadCount: Int,
starredCount: Int,
modifier: Modifier = Modifier,
) {
if (unreadCount > 0 || starredCount > 0) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MainTheme.spacings.default),
) {
val resources = LocalContext.current.resources
if (unreadCount > 0) {
NavigationDrawerItemBadge(
label = labelForCount(
count = unreadCount,
resources = resources,
),
imageVector = Icons.Filled.Dot,
)
}
if (starredCount > 0) {
NavigationDrawerItemBadge(
label = labelForCount(
count = starredCount,
resources = resources,
),
imageVector = Icons.Filled.Star,
)
}
}
}
}

View file

@ -0,0 +1,40 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.setting
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import app.k9mail.core.ui.compose.designsystem.atom.Surface
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icon
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
internal fun SettingItem(
icon: ImageVector,
label: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier.width(MainTheme.sizes.large),
contentAlignment = Alignment.Center,
) {
Surface(
color = MainTheme.colors.surfaceContainer,
shape = CircleShape,
) {
Icon(
imageVector = icon,
contentDescription = label,
modifier = Modifier
.clickable(onClick = onClick)
.padding(MainTheme.spacings.oneHalf),
)
}
}
}

View file

@ -0,0 +1,48 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.setting
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
import app.k9mail.core.ui.compose.theme2.MainTheme
import net.thunderbird.feature.navigation.drawer.siderail.R
@Composable
internal fun SettingList(
onAccountSelectorClick: () -> Unit,
onManageFoldersClick: () -> Unit,
showAccountSelector: Boolean,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.padding(vertical = MainTheme.spacings.default)
.windowInsetsPadding(WindowInsets.navigationBars)
.fillMaxWidth(),
) {
SettingListItem(
label = stringResource(R.string.navigation_drawer_siderail_action_manage_folders),
onClick = onManageFoldersClick,
imageVector = Icons.Outlined.FolderManaged,
)
SettingListItem(
label = if (showAccountSelector) {
stringResource(R.string.navigation_drawer_siderail_action_hide_accounts)
} else {
stringResource(R.string.navigation_drawer_siderail_action_show_accounts)
},
onClick = onAccountSelectorClick,
imageVector = if (showAccountSelector) {
Icons.Outlined.ChevronLeft
} else {
Icons.Outlined.ChevronRight
},
)
}
}

View file

@ -0,0 +1,27 @@
package net.thunderbird.feature.navigation.drawer.siderail.ui.setting
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import app.k9mail.core.ui.compose.designsystem.atom.icon.Icon
import app.k9mail.core.ui.compose.designsystem.organism.drawer.NavigationDrawerItem
@Composable
internal fun SettingListItem(
label: String,
onClick: () -> Unit,
imageVector: ImageVector,
modifier: Modifier = Modifier,
) {
NavigationDrawerItem(
label = label,
onClick = onClick,
modifier = modifier,
selected = false,
icon = {
Icon(
imageVector = imageVector,
)
},
)
}

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">الإعدادات</string>
<string name="navigation_drawer_siderail_action_manage_folders">إدارة المجلدات</string>
<string name="navigation_drawer_siderail_unified_inbox_title">البريد الوارد الموحَّد</string>
<string name="navigation_drawer_siderail_action_hide_accounts">إخفاء الحسابات</string>
<string name="navigation_drawer_siderail_action_show_accounts">إظهار الحسابات</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">مزامنة جميع الحسابات</string>
</resources>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Налады</string>
<string name="navigation_drawer_siderail_action_manage_folders">Кіраванне каталогамі</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Усе атрыманыя</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Сінхранізаваць усе акаўнты</string>
<string name="navigation_drawer_siderail_action_show_accounts">Паказаць акаўнты</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Схаваць акаўнты</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1000+</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Настройки</string>
<string name="navigation_drawer_siderail_action_manage_folders">Управление на папки</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Обща входяща кутия</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Синхронизация на всички профили</string>
<string name="navigation_drawer_siderail_action_show_accounts">Показване на профилите</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Скриване на профилите</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">над 99</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">над 1000</string>
</resources>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Arventennoù</string>
<string name="navigation_drawer_siderail_action_manage_folders">Merañ an teuliadoù</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Boest degemer unanet</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Postavke</string>
<string name="navigation_drawer_siderail_action_manage_folders">Upravljajte direktorijumima</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Configuració</string>
<string name="navigation_drawer_siderail_action_manage_folders">Gestioneu les carpetes</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Bústia d\'entrada unificada</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Sincronitza tots els comptes</string>
<string name="navigation_drawer_siderail_action_show_accounts">Mostra els comptes</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Amaga els comptes</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1000+</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Parametri</string>
<string name="navigation_drawer_siderail_action_manage_folders">Ghjestione di i cartulari</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Ricezzione cuncolta</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Sincrunizà tutti i conti</string>
<string name="navigation_drawer_siderail_action_show_accounts">Affissà i conti</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">&gt; 99</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Piattà i conti</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">&gt; 1000</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Nastavení</string>
<string name="navigation_drawer_siderail_action_manage_folders">Správa složek</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Jednotná schránka</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1 tis.+</string>
<string name="navigation_drawer_siderail_action_show_accounts">Zobrazit účty</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Synchronizovat všechny účty</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Skrýt účty</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Gosodiadau</string>
<string name="navigation_drawer_siderail_action_manage_folders">Rheoli ffolderi</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Mewnflwch Unedig</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Cydweddu pob cyfrif</string>
<string name="navigation_drawer_siderail_action_show_accounts">Dangos cyfrifon</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Cuddio cyfrifon</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Indstillinger</string>
<string name="navigation_drawer_siderail_action_manage_folders">Håndtér mapper</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Fælles indbakke</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Einstellungen</string>
<string name="navigation_drawer_siderail_action_manage_folders">Ordner verwalten</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Gemeinsamer Posteingang</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Konten ausblenden</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Alle Konten synchronisieren</string>
<string name="navigation_drawer_siderail_action_show_accounts">Konten anzeigen</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Ρυθμίσεις</string>
<string name="navigation_drawer_siderail_action_manage_folders">Διαχείριση φακέλων</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Ενιαία Εισερχόμενα</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1χ+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Συγχρονισμός όλων των λογαριασμών</string>
<string name="navigation_drawer_siderail_action_show_accounts">Εμφάνιση λογαριασμών</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Απόκρυψη λογαριασμών</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Settings</string>
<string name="navigation_drawer_siderail_action_manage_folders">Manage folders</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Unified Inbox</string>
</resources>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Agordoj</string>
<string name="navigation_drawer_siderail_action_manage_folders">Administri mesaĝujojn</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Unuigita ricevujo</string>
<string name="navigation_drawer_siderail_action_show_accounts">Montri kontojn</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Samtempigi ĉiujn kontojn</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Kaŝi kontojn</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Ajustes</string>
<string name="navigation_drawer_siderail_action_manage_folders">Administrar carpetas</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Entrada unificada</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">&gt;99</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">&gt;1000</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Sincronizar todas las cuentas</string>
<string name="navigation_drawer_siderail_action_show_accounts">Mostrar cuentas</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Ocultar cuentas</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Seadistused</string>
<string name="navigation_drawer_siderail_action_manage_folders">Halda kaustu</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Koondsisendkaust</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Sünkroniseeri kõik kontod</string>
<string name="navigation_drawer_siderail_action_show_accounts">Näita kontosid</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Peida kontod</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Ezarpenak</string>
<string name="navigation_drawer_siderail_action_manage_folders">Kudeatu karpetak</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Sarrerako ontzi bateratua</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Sinkronizatu kontu guztiak</string>
<string name="navigation_drawer_siderail_action_show_accounts">Erakutsi kontuak</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Ezkutatu kontuak</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1.000+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">تنظیمات</string>
<string name="navigation_drawer_siderail_action_manage_folders">مدیریت پوشه‌ها</string>
<string name="navigation_drawer_siderail_unified_inbox_title">صندوق ورودی یکپارچه</string>
<string name="navigation_drawer_siderail_action_hide_accounts">نهفتن حساب‌ها</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">هم‌گام سازی همهٔ آشنایان</string>
<string name="navigation_drawer_siderail_action_show_accounts">نمایش حساب‌ها</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">۹۹+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">ه+</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Asetukset</string>
<string name="navigation_drawer_siderail_action_manage_folders">Hallitse kansioita</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Yhdistetty saapuneet</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Synkronoi kaikki tilit</string>
<string name="navigation_drawer_siderail_action_show_accounts">Näytä tilit</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Piilota tilit</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Paramètres</string>
<string name="navigation_drawer_siderail_action_manage_folders">Gérer les dossiers</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Boîte de réception unifiée</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Cacher les comptes</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">&gt;99</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">&gt;1 k</string>
<string name="navigation_drawer_siderail_action_show_accounts">Afficher les comptes</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Synchroniser tous les comptes</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Ynstellingen</string>
<string name="navigation_drawer_siderail_action_manage_folders">Mappen beheare</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Kombinearre Postfek YN</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Alle accounts syngronisearje</string>
<string name="navigation_drawer_siderail_action_show_accounts">Accounts toane</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Accounts ferstopje</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="navigation_drawer_siderail_action_settings">Socruithe</string>
<string name="navigation_drawer_siderail_action_manage_folders">Bainistigh fillteáin</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Sioncronaigh gach cuntas</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
<string name="navigation_drawer_siderail_action_show_accounts">Taispeáin cuntais</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Folaigh cuntais</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Bosca Isteach Aontaithe</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Roghainnean</string>
<string name="navigation_drawer_siderail_action_manage_folders">Stiùirich na pasganan</string>
<string name="navigation_drawer_siderail_unified_inbox_title">An t-oll-bhogsa</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">Còrr is 99</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">Còrr is 1k</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Sioncronaich a h-uile cunntas</string>
<string name="navigation_drawer_siderail_action_show_accounts">Seall na cunntasan</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Falaich na cunntasan</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Configuración</string>
<string name="navigation_drawer_siderail_action_manage_folders">Xestionar cartafoles</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Entrada unificada</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="navigation_drawer_siderail_action_settings">સૅટિંગ</string>
<string name="navigation_drawer_siderail_action_manage_folders">ફોલ્ડર મેનેજ કરોં</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">બધાં ખાતાઓ ને સિંક કરોં</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">૧૦૦૦+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">૯૯+</string>
<string name="navigation_drawer_siderail_action_show_accounts">ખાતાઓ દેખાડોં</string>
<string name="navigation_drawer_siderail_action_hide_accounts">ખાતાઓ છુપાડોં</string>
<string name="navigation_drawer_siderail_unified_inbox_title">એકીકૃત ઇનબોક્સ</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">सेटिंग</string>
<string name="navigation_drawer_siderail_action_manage_folders">फोल्डर मैनेज करें</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Podešenja</string>
<string name="navigation_drawer_siderail_action_manage_folders">Upravljanje mapama</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Objedinjena dolazna pošta</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Sinkroniziraj sve račune</string>
<string name="navigation_drawer_siderail_action_show_accounts">Prikaži račune</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Sakrij račune</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Beállítások</string>
<string name="navigation_drawer_siderail_action_manage_folders">Mappák kezelése</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Egységes beérkezett üzenetek</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Összes fiók szinkronizálása</string>
<string name="navigation_drawer_siderail_action_show_accounts">Fiókok megjelenítése</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Fiókok elrejtése</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1e+</string>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Կարգաւորումներ</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Pengaturan</string>
<string name="navigation_drawer_siderail_action_manage_folders">Kelola folder</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Kotak Masuk Terpadu</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1rb+</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Sinkronkan semua akun</string>
<string name="navigation_drawer_siderail_action_show_accounts">Tampilkan akun</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Sembunyikan akun</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Stillingar</string>
<string name="navigation_drawer_siderail_action_manage_folders">Sýsla með möppur</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Sameinað innhólf</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">&gt;99</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">&gt;1.000</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Samstilla alla reikninga</string>
<string name="navigation_drawer_siderail_action_show_accounts">Sýna reikninga</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Fela reikninga</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Impostazioni</string>
<string name="navigation_drawer_siderail_action_manage_folders">Gestione cartelle</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Posta combinata</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Nascondi gli account</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Sincronizza tutti gli account</string>
<string name="navigation_drawer_siderail_action_show_accounts">Mostra gli account</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">הגדרות</string>
<string name="navigation_drawer_siderail_action_manage_folders">נהל תיקיות</string>
<string name="navigation_drawer_siderail_unified_inbox_title">תיבת דואר נכנס אחידה</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">סנכרן את כל החשבונות</string>
<string name="navigation_drawer_siderail_action_show_accounts">הצג חשבונות</string>
<string name="navigation_drawer_siderail_action_hide_accounts">הסתר חשבונות</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">設定</string>
<string name="navigation_drawer_siderail_action_manage_folders">フォルダーを管理</string>
<string name="navigation_drawer_siderail_unified_inbox_title">統合受信トレイ</string>
<string name="navigation_drawer_siderail_action_show_accounts">アカウントを開く</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">すべてのアカウントを同期</string>
<string name="navigation_drawer_siderail_action_hide_accounts">アカウントを閉じる</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1000+</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">პარამეტრები</string>
<string name="navigation_drawer_siderail_action_manage_folders">საკეცების მართვა</string>
<string name="navigation_drawer_siderail_unified_inbox_title">გაერთიანებული შემავალი</string>
</resources>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="navigation_drawer_siderail_action_settings">Iɣewwaren</string>
<string name="navigation_drawer_siderail_action_manage_folders">Sefrek ikaramen</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Mtawi meṛṛa imiḍanen</string>
<string name="navigation_drawer_siderail_action_show_accounts">Sken imiḍanen</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Ffer imiḍanen</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="navigation_drawer_siderail_action_settings">Баптаулар</string>
<string name="navigation_drawer_siderail_action_show_accounts">Тіркелгілерді көрсету</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">설정</string>
<string name="navigation_drawer_siderail_action_manage_folders">폴더 관리</string>
<string name="navigation_drawer_siderail_unified_inbox_title">통합 편지함</string>
</resources>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Nustatymai</string>
<string name="navigation_drawer_siderail_action_manage_folders">Tvarkyti aplankus</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Suvestiniai gautieji</string>
<string name="navigation_drawer_siderail_action_show_accounts">Rodyti paskyras</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Sinchronizuoti visas paskyras</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Iestatījumi</string>
<string name="navigation_drawer_siderail_action_manage_folders">Pārvaldīt mapes</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Apvienotā Iesūtne</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">സജ്ജീകരണങ്ങൾ</string>
<string name="navigation_drawer_siderail_action_manage_folders">ഫോൾഡറുകൾ നിയന്ത്രിക്കുക</string>
<string name="navigation_drawer_siderail_unified_inbox_title">ഏകീകൃത ഇൻ‌ബോക്സ്</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Innstillinger</string>
<string name="navigation_drawer_siderail_action_manage_folders">Behandle mapper</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Samlet innboks</string>
<string name="navigation_drawer_siderail_action_show_accounts">Vis kontoer</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Synkronisér alle kontoer</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Skjul kontoer</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1T+</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Instellingen</string>
<string name="navigation_drawer_siderail_action_manage_folders">Mappen beheren</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Samengevoegd Postvak IN</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Alle accounts synchroniseren</string>
<string name="navigation_drawer_siderail_action_show_accounts">Accounts tonen</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Accounts verbergen</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Innstillingar</string>
<string name="navigation_drawer_siderail_action_manage_folders">Handsam mapper</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Synkroniser alle kontoar</string>
<string name="navigation_drawer_siderail_action_show_accounts">Vis kontoar</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Skjul kontoar</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Samla innboks</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Ustawienia</string>
<string name="navigation_drawer_siderail_action_manage_folders">Zarządzaj folderami</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Zintegrowana odbiorcza</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Synchronizuj wszystkie konta</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Ukryj konta</string>
<string name="navigation_drawer_siderail_action_show_accounts">Pokaż konta</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Configurações</string>
<string name="navigation_drawer_siderail_action_manage_folders">Gerenciar pastas</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Caixa de Entrada Unificada</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Sincronizar todas as contas</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Esconder contas</string>
<string name="navigation_drawer_siderail_action_show_accounts">Mostrar contas</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Configurações</string>
<string name="navigation_drawer_siderail_action_manage_folders">Gerir pastas</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Caixa de entrada unificada</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Sincronizar todas as contas</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Esconder contas</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">&gt;99</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">&gt;1k</string>
<string name="navigation_drawer_siderail_action_show_accounts">Mostrar contas</string>
</resources>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="navigation_drawer_siderail_action_settings">Opțiuni</string>
<string name="navigation_drawer_siderail_action_manage_folders">Gestionează dosarele</string>
<string name="navigation_drawer_siderail_unified_inbox_title">Căsuță poștală unificată</string>
<string name="navigation_drawer_siderail_action_sync_all_accounts">Sincronizează toate conturile</string>
<string name="navigation_drawer_siderail_action_show_accounts">Arată conturile</string>
<string name="navigation_drawer_siderail_action_hide_accounts">Ascunde conturile</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_99">99+</string>
<string name="navigation_drawer_siderail_folder_item_badge_count_greater_than_1_000">1k+</string>
</resources>

Some files were not shown because too many files have changed in this diff Show more