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,17 @@
plugins {
id(ThunderbirdPlugins.Library.androidCompose)
}
android {
namespace = "app.k9mail.feature.account.common"
resourcePrefix = "account_common_"
}
dependencies {
implementation(projects.core.ui.compose.designsystem)
implementation(projects.core.common)
implementation(projects.mail.common)
testImplementation(projects.core.ui.compose.testing)
}

View file

@ -0,0 +1,15 @@
package app.k9mail.feature.account.common.ui
import androidx.compose.runtime.Composable
import app.k9mail.core.ui.compose.common.annotation.PreviewDevices
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@PreviewDevices
internal fun AccountTopAppBarPreview() {
PreviewWithThemes {
AccountTopAppBar(
title = "Title",
)
}
}

View file

@ -0,0 +1,13 @@
package app.k9mail.feature.account.common.ui
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 AppTitleTopHeaderPreview() {
PreviewWithThemes {
AppTitleTopHeader(title = "Title")
}
}

View file

@ -0,0 +1,24 @@
package app.k9mail.feature.account.common.ui
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.text.TextTitleMedium
@Composable
@Preview(showBackground = true)
internal fun ContentListViewPreview() {
PreviewWithThemes {
ContentListView {
item {
TextTitleMedium("Item 1")
}
item {
TextTitleMedium("Item 2")
}
item {
TextTitleMedium("Item 3")
}
}
}
}

View file

@ -0,0 +1,59 @@
package app.k9mail.feature.account.common.ui
import androidx.compose.runtime.Composable
import app.k9mail.core.ui.compose.common.annotation.PreviewDevices
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
@Composable
@PreviewDevices
internal fun WizardNavigationBarPreview() {
PreviewWithThemes {
WizardNavigationBar(
onNextClick = {},
onBackClick = {},
)
}
}
@Composable
@PreviewDevices
internal fun WizardNavigationBarDisabledPreview() {
PreviewWithThemes {
WizardNavigationBar(
onNextClick = {},
onBackClick = {},
state = WizardNavigationBarState(
isNextEnabled = false,
isBackEnabled = false,
),
)
}
}
@Composable
@PreviewDevices
internal fun WizardNavigationBarHideNextPreview() {
PreviewWithThemes {
WizardNavigationBar(
onNextClick = {},
onBackClick = {},
state = WizardNavigationBarState(
showNext = false,
),
)
}
}
@Composable
@PreviewDevices
internal fun WizardNavigationBarHideBackPreview() {
PreviewWithThemes {
WizardNavigationBar(
onNextClick = {},
onBackClick = {},
state = WizardNavigationBarState(
showBack = false,
),
)
}
}

View file

@ -0,0 +1,57 @@
package app.k9mail.feature.account.common.ui.fake
import app.k9mail.feature.account.common.domain.AccountDomainContract
import app.k9mail.feature.account.common.domain.entity.AccountDisplayOptions
import app.k9mail.feature.account.common.domain.entity.AccountState
import app.k9mail.feature.account.common.domain.entity.AccountSyncOptions
import app.k9mail.feature.account.common.domain.entity.AuthorizationState
import app.k9mail.feature.account.common.domain.entity.MailConnectionSecurity
import app.k9mail.feature.account.common.domain.entity.SpecialFolderSettings
import com.fsck.k9.mail.AuthType
import com.fsck.k9.mail.ServerSettings
@Suppress("TooManyFunctions")
class FakeAccountStateRepository : AccountDomainContract.AccountStateRepository {
override fun getState(): AccountState = AccountState(
emailAddress = "test@example.com",
incomingServerSettings = ServerSettings(
type = "imap",
host = "imap.example.com",
port = 993,
connectionSecurity = MailConnectionSecurity.SSL_TLS_REQUIRED,
authenticationType = AuthType.PLAIN,
username = "test",
password = "password",
clientCertificateAlias = null,
),
outgoingServerSettings = ServerSettings(
type = "smtp",
host = "smtp.example.com",
port = 465,
connectionSecurity = MailConnectionSecurity.SSL_TLS_REQUIRED,
authenticationType = AuthType.PLAIN,
username = "test",
password = "password",
clientCertificateAlias = null,
),
)
override fun setState(accountState: AccountState) = Unit
override fun setEmailAddress(emailAddress: String) = Unit
override fun setIncomingServerSettings(serverSettings: ServerSettings) = Unit
override fun setOutgoingServerSettings(serverSettings: ServerSettings) = Unit
override fun setAuthorizationState(authorizationState: AuthorizationState) = Unit
override fun setSpecialFolderSettings(specialFolderSettings: SpecialFolderSettings) = Unit
override fun setDisplayOptions(displayOptions: AccountDisplayOptions) = Unit
override fun setSyncOptions(syncOptions: AccountSyncOptions) = Unit
override fun clear() = Unit
}

View file

@ -0,0 +1,10 @@
package app.k9mail.feature.account.common
import app.k9mail.feature.account.common.domain.entity.AccountState
interface AccountCommonExternalContract {
fun interface AccountStateLoader {
suspend fun loadAccountState(accountUuid: String): AccountState?
}
}

View file

@ -0,0 +1,17 @@
package app.k9mail.feature.account.common
import app.k9mail.feature.account.common.data.InMemoryAccountStateRepository
import app.k9mail.feature.account.common.domain.AccountDomainContract
import com.fsck.k9.mail.oauth.AuthStateStorage
import net.thunderbird.core.common.coreCommonModule
import org.koin.core.module.Module
import org.koin.dsl.binds
import org.koin.dsl.module
val featureAccountCommonModule: Module = module {
includes(coreCommonModule)
single {
InMemoryAccountStateRepository()
}.binds(arrayOf(AccountDomainContract.AccountStateRepository::class, AuthStateStorage::class))
}

View file

@ -0,0 +1,64 @@
package app.k9mail.feature.account.common.data
import app.k9mail.feature.account.common.domain.AccountDomainContract
import app.k9mail.feature.account.common.domain.entity.AccountDisplayOptions
import app.k9mail.feature.account.common.domain.entity.AccountState
import app.k9mail.feature.account.common.domain.entity.AccountSyncOptions
import app.k9mail.feature.account.common.domain.entity.AuthorizationState
import app.k9mail.feature.account.common.domain.entity.SpecialFolderSettings
import com.fsck.k9.mail.ServerSettings
import com.fsck.k9.mail.oauth.AuthStateStorage
@Suppress("TooManyFunctions")
class InMemoryAccountStateRepository(
private var state: AccountState = AccountState(),
) : AccountDomainContract.AccountStateRepository, AuthStateStorage {
override fun getState(): AccountState {
return state
}
override fun setState(accountState: AccountState) {
state = accountState
}
override fun setEmailAddress(emailAddress: String) {
state = state.copy(emailAddress = emailAddress)
}
override fun setIncomingServerSettings(serverSettings: ServerSettings) {
state = state.copy(incomingServerSettings = serverSettings)
}
override fun setOutgoingServerSettings(serverSettings: ServerSettings) {
state = state.copy(outgoingServerSettings = serverSettings)
}
override fun setAuthorizationState(authorizationState: AuthorizationState) {
state = state.copy(authorizationState = authorizationState)
}
override fun setSpecialFolderSettings(specialFolderSettings: SpecialFolderSettings) {
state = state.copy(specialFolderSettings = specialFolderSettings)
}
override fun setDisplayOptions(displayOptions: AccountDisplayOptions) {
state = state.copy(displayOptions = displayOptions)
}
override fun setSyncOptions(syncOptions: AccountSyncOptions) {
state = state.copy(syncOptions = syncOptions)
}
override fun clear() {
state = AccountState()
}
override fun getAuthorizationState(): String? {
return state.authorizationState?.value
}
override fun updateAuthorizationState(authorizationState: String?) {
state = state.copy(authorizationState = AuthorizationState(authorizationState))
}
}

View file

@ -0,0 +1,34 @@
package app.k9mail.feature.account.common.domain
import app.k9mail.feature.account.common.domain.entity.AccountDisplayOptions
import app.k9mail.feature.account.common.domain.entity.AccountState
import app.k9mail.feature.account.common.domain.entity.AccountSyncOptions
import app.k9mail.feature.account.common.domain.entity.AuthorizationState
import app.k9mail.feature.account.common.domain.entity.SpecialFolderSettings
import com.fsck.k9.mail.ServerSettings
interface AccountDomainContract {
@Suppress("TooManyFunctions")
interface AccountStateRepository {
fun getState(): AccountState
fun setState(accountState: AccountState)
fun setEmailAddress(emailAddress: String)
fun setIncomingServerSettings(serverSettings: ServerSettings)
fun setOutgoingServerSettings(serverSettings: ServerSettings)
fun setAuthorizationState(authorizationState: AuthorizationState)
fun setSpecialFolderSettings(specialFolderSettings: SpecialFolderSettings)
fun setDisplayOptions(displayOptions: AccountDisplayOptions)
fun setSyncOptions(syncOptions: AccountSyncOptions)
fun clear()
}
}

View file

@ -0,0 +1,13 @@
package app.k9mail.feature.account.common.domain.entity
import com.fsck.k9.mail.ServerSettings
data class Account(
val uuid: String,
val emailAddress: String,
val incomingServerSettings: ServerSettings,
val outgoingServerSettings: ServerSettings,
val authorizationState: String?,
val specialFolderSettings: SpecialFolderSettings?,
val options: AccountOptions,
)

View file

@ -0,0 +1,7 @@
package app.k9mail.feature.account.common.domain.entity
data class AccountDisplayOptions(
val accountName: String,
val displayName: String,
val emailSignature: String?,
)

View file

@ -0,0 +1,10 @@
package app.k9mail.feature.account.common.domain.entity
data class AccountOptions(
val accountName: String,
val displayName: String,
val emailSignature: String?,
val checkFrequencyInMinutes: Int,
val messageDisplayCount: Int,
val showNotification: Boolean,
)

View file

@ -0,0 +1,14 @@
package app.k9mail.feature.account.common.domain.entity
import com.fsck.k9.mail.ServerSettings
data class AccountState(
val uuid: String? = null,
val emailAddress: String? = null,
val incomingServerSettings: ServerSettings? = null,
val outgoingServerSettings: ServerSettings? = null,
val authorizationState: AuthorizationState? = null,
val specialFolderSettings: SpecialFolderSettings? = null,
val displayOptions: AccountDisplayOptions? = null,
val syncOptions: AccountSyncOptions? = null,
)

View file

@ -0,0 +1,7 @@
package app.k9mail.feature.account.common.domain.entity
data class AccountSyncOptions(
val checkFrequencyInMinutes: Int,
val messageDisplayCount: Int,
val showNotification: Boolean,
)

View file

@ -0,0 +1,58 @@
package app.k9mail.feature.account.common.domain.entity
import com.fsck.k9.mail.AuthType
import kotlinx.collections.immutable.toImmutableList
enum class AuthenticationType(
val isUsernameRequired: Boolean,
val isPasswordRequired: Boolean,
) {
None(
isUsernameRequired = false,
isPasswordRequired = false,
),
PasswordCleartext(
isUsernameRequired = true,
isPasswordRequired = true,
),
PasswordEncrypted(
isUsernameRequired = true,
isPasswordRequired = true,
),
ClientCertificate(
isUsernameRequired = true,
isPasswordRequired = false,
),
OAuth2(
isUsernameRequired = true,
isPasswordRequired = false,
),
;
companion object {
val DEFAULT = PasswordCleartext
fun all() = entries.toImmutableList()
fun outgoing() = all()
}
}
fun AuthenticationType.toAuthType(): AuthType {
return when (this) {
AuthenticationType.None -> AuthType.NONE
AuthenticationType.PasswordCleartext -> AuthType.PLAIN
AuthenticationType.PasswordEncrypted -> AuthType.CRAM_MD5
AuthenticationType.ClientCertificate -> AuthType.EXTERNAL
AuthenticationType.OAuth2 -> AuthType.XOAUTH2
}
}
fun AuthType.toAuthenticationType(): AuthenticationType {
return when (this) {
AuthType.PLAIN -> AuthenticationType.PasswordCleartext
AuthType.CRAM_MD5 -> AuthenticationType.PasswordEncrypted
AuthType.EXTERNAL -> AuthenticationType.ClientCertificate
AuthType.XOAUTH2 -> AuthenticationType.OAuth2
AuthType.NONE -> AuthenticationType.None
}
}

View file

@ -0,0 +1,5 @@
package app.k9mail.feature.account.common.domain.entity
data class AuthorizationState(
val value: String? = null,
)

View file

@ -0,0 +1,61 @@
package app.k9mail.feature.account.common.domain.entity
import app.k9mail.feature.account.common.domain.entity.ConnectionSecurity.None
import app.k9mail.feature.account.common.domain.entity.ConnectionSecurity.StartTLS
import app.k9mail.feature.account.common.domain.entity.ConnectionSecurity.TLS
import kotlinx.collections.immutable.toImmutableList
enum class ConnectionSecurity {
None,
StartTLS,
TLS,
;
companion object {
val DEFAULT = TLS
fun all() = entries.toImmutableList()
}
}
fun ConnectionSecurity.toMailConnectionSecurity(): MailConnectionSecurity {
return when (this) {
None -> MailConnectionSecurity.NONE
StartTLS -> MailConnectionSecurity.STARTTLS_REQUIRED
TLS -> MailConnectionSecurity.SSL_TLS_REQUIRED
}
}
fun MailConnectionSecurity.toConnectionSecurity(): ConnectionSecurity {
return when (this) {
MailConnectionSecurity.NONE -> None
MailConnectionSecurity.STARTTLS_REQUIRED -> StartTLS
MailConnectionSecurity.SSL_TLS_REQUIRED -> TLS
}
}
@Suppress("MagicNumber")
fun ConnectionSecurity.toSmtpDefaultPort(): Long {
return when (this) {
None -> 587
StartTLS -> 587
TLS -> 465
}
}
@Suppress("MagicNumber")
fun ConnectionSecurity.toImapDefaultPort(): Long {
return when (this) {
None -> 143
StartTLS -> 143
TLS -> 993
}
}
@Suppress("MagicNumber")
fun ConnectionSecurity.toPop3DefaultPort(): Long {
return when (this) {
None -> 110
StartTLS -> 110
TLS -> 995
}
}

View file

@ -0,0 +1,29 @@
package app.k9mail.feature.account.common.domain.entity
import kotlinx.collections.immutable.toImmutableList
enum class IncomingProtocolType(
val defaultName: String,
val defaultConnectionSecurity: ConnectionSecurity,
) {
IMAP("imap", ConnectionSecurity.TLS),
POP3("pop3", ConnectionSecurity.TLS),
;
companion object {
val DEFAULT = IMAP
fun all() = entries.toImmutableList()
fun fromName(name: String): IncomingProtocolType {
return entries.find { it.defaultName == name } ?: throw IllegalArgumentException("Unknown protocol: $name")
}
}
}
fun IncomingProtocolType.toDefaultPort(connectionSecurity: ConnectionSecurity): Long {
return when (this) {
IncomingProtocolType.IMAP -> connectionSecurity.toImapDefaultPort()
IncomingProtocolType.POP3 -> connectionSecurity.toPop3DefaultPort()
}
}

View file

@ -0,0 +1,9 @@
package app.k9mail.feature.account.common.domain.entity
/**
* Enum representing the mode a user is interacting with an account or setting.
*/
enum class InteractionMode {
Create,
Edit,
}

View file

@ -0,0 +1,3 @@
package app.k9mail.feature.account.common.domain.entity
typealias MailConnectionSecurity = com.fsck.k9.mail.ConnectionSecurity

View file

@ -0,0 +1,23 @@
package app.k9mail.feature.account.common.domain.entity
import kotlinx.collections.immutable.toImmutableList
enum class OutgoingProtocolType(
val defaultName: String,
val defaultConnectionSecurity: ConnectionSecurity,
) {
SMTP("smtp", ConnectionSecurity.TLS),
;
companion object {
val DEFAULT = SMTP
fun all() = entries.toImmutableList()
}
}
fun OutgoingProtocolType.toDefaultPort(connectionSecurity: ConnectionSecurity): Long {
return when (this) {
OutgoingProtocolType.SMTP -> connectionSecurity.toSmtpDefaultPort()
}
}

View file

@ -0,0 +1,18 @@
package app.k9mail.feature.account.common.domain.entity
import com.fsck.k9.mail.folders.RemoteFolder
sealed interface SpecialFolderOption {
data class None(
val isAutomatic: Boolean = false,
) : SpecialFolderOption
data class Regular(
val remoteFolder: RemoteFolder,
) : SpecialFolderOption
data class Special(
val isAutomatic: Boolean = false,
val remoteFolder: RemoteFolder,
) : SpecialFolderOption
}

View file

@ -0,0 +1,9 @@
package app.k9mail.feature.account.common.domain.entity
data class SpecialFolderOptions(
val archiveSpecialFolderOptions: List<SpecialFolderOption>,
val draftsSpecialFolderOptions: List<SpecialFolderOption>,
val sentSpecialFolderOptions: List<SpecialFolderOption>,
val spamSpecialFolderOptions: List<SpecialFolderOption>,
val trashSpecialFolderOptions: List<SpecialFolderOption>,
)

View file

@ -0,0 +1,9 @@
package app.k9mail.feature.account.common.domain.entity
data class SpecialFolderSettings(
val archiveSpecialFolderOption: SpecialFolderOption,
val draftsSpecialFolderOption: SpecialFolderOption,
val sentSpecialFolderOption: SpecialFolderOption,
val spamSpecialFolderOption: SpecialFolderOption,
val trashSpecialFolderOption: SpecialFolderOption,
)

View file

@ -0,0 +1,74 @@
package app.k9mail.feature.account.common.domain.input
import net.thunderbird.core.common.domain.usecase.validation.ValidationError
import net.thunderbird.core.common.domain.usecase.validation.ValidationResult
class BooleanInputField(
override val value: Boolean? = null,
override val error: ValidationError? = null,
override val isValid: Boolean = false,
) : InputField<Boolean?> {
override fun updateValue(value: Boolean?): BooleanInputField {
return BooleanInputField(
value = value,
error = null,
isValid = false,
)
}
override fun updateError(error: ValidationError?): BooleanInputField {
return BooleanInputField(
value = value,
error = error,
isValid = false,
)
}
override fun updateValidity(isValid: Boolean): BooleanInputField {
if (isValid == this.isValid) return this
return BooleanInputField(
value = value,
error = null,
isValid = isValid,
)
}
override fun updateFromValidationResult(result: ValidationResult): BooleanInputField {
return when (result) {
is ValidationResult.Success -> BooleanInputField(
value = value,
error = null,
isValid = true,
)
is ValidationResult.Failure -> BooleanInputField(
value = value,
error = result.error,
isValid = false,
)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as BooleanInputField
if (value != other.value) return false
if (error != other.error) return false
return isValid == other.isValid
}
override fun hashCode(): Int {
var result = value?.hashCode() ?: 0
result = 31 * result + (error?.hashCode() ?: 0)
result = 31 * result + isValid.hashCode()
return result
}
override fun toString(): String {
return "BooleanInputField(value=$value, error=$error, isValid=$isValid)"
}
}

View file

@ -0,0 +1,48 @@
package app.k9mail.feature.account.common.domain.input
import net.thunderbird.core.common.domain.usecase.validation.ValidationError
import net.thunderbird.core.common.domain.usecase.validation.ValidationResult
/**
* InputField is an interface defining the state of an input field.
*
* @param T The type of the value the input field holds.
*/
interface InputField<T> {
val value: T
val error: ValidationError?
val isValid: Boolean
/**
* Updates the current value of the input field.
*
* @param value The new value to be set for the input field.
* @return a new InputField instance with the updated value.
*/
fun updateValue(value: T): InputField<T>
/**
* Updates the current error of the input field.
*
* @param error The new error to be set for the input field.
*/
fun updateError(error: ValidationError?): InputField<T>
/**
* Updates the current validity of the input field.
*
* @param isValid The new validity to be set for the input field.
*/
fun updateValidity(isValid: Boolean): InputField<T>
/**
* Checks if the input field currently has an error.
*
* @return a Boolean indicating whether the input field has an error.
*/
fun hasError(): Boolean {
return error != null
}
fun updateFromValidationResult(result: ValidationResult): InputField<T>
}

View file

@ -0,0 +1,75 @@
package app.k9mail.feature.account.common.domain.input
import net.thunderbird.core.common.domain.usecase.validation.ValidationError
import net.thunderbird.core.common.domain.usecase.validation.ValidationResult
class NumberInputField(
override val value: Long? = null,
override val error: ValidationError? = null,
override val isValid: Boolean = false,
) : InputField<Long?> {
override fun updateValue(value: Long?): NumberInputField {
return NumberInputField(
value = value,
error = null,
isValid = false,
)
}
override fun updateError(error: ValidationError?): NumberInputField {
return NumberInputField(
value = value,
error = error,
isValid = false,
)
}
override fun updateValidity(isValid: Boolean): NumberInputField {
if (isValid == this.isValid) return this
return NumberInputField(
value = value,
error = null,
isValid = isValid,
)
}
override fun updateFromValidationResult(result: ValidationResult): NumberInputField {
return when (result) {
is ValidationResult.Success -> NumberInputField(
value = value,
error = null,
isValid = true,
)
is ValidationResult.Failure -> NumberInputField(
value = value,
error = result.error,
isValid = false,
)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as NumberInputField
if (value != other.value) return false
if (error != other.error) return false
return isValid == other.isValid
}
override fun hashCode(): Int {
var result = value?.hashCode() ?: 0
result = 31 * result + (error?.hashCode() ?: 0)
result = 31 * result + isValid.hashCode()
return result
}
override fun toString(): String {
return "NumberInputField(value=$value, error=$error, isValid=$isValid)"
}
}

View file

@ -0,0 +1,75 @@
package app.k9mail.feature.account.common.domain.input
import net.thunderbird.core.common.domain.usecase.validation.ValidationError
import net.thunderbird.core.common.domain.usecase.validation.ValidationResult
class StringInputField(
override val value: String = "",
override val error: ValidationError? = null,
override val isValid: Boolean = false,
) : InputField<String> {
override fun updateValue(value: String): StringInputField {
return StringInputField(
value = value,
error = null,
isValid = false,
)
}
override fun updateError(error: ValidationError?): StringInputField {
return StringInputField(
value = value,
error = error,
isValid = false,
)
}
override fun updateValidity(isValid: Boolean): StringInputField {
if (isValid == this.isValid) return this
return StringInputField(
value = value,
error = null,
isValid = isValid,
)
}
override fun updateFromValidationResult(result: ValidationResult): StringInputField {
return when (result) {
is ValidationResult.Success -> StringInputField(
value = value,
error = null,
isValid = true,
)
is ValidationResult.Failure -> StringInputField(
value = value,
error = result.error,
isValid = false,
)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as StringInputField
if (value != other.value) return false
if (error != other.error) return false
return isValid == other.isValid
}
override fun hashCode(): Int {
var result = value.hashCode()
result = 31 * result + (error?.hashCode() ?: 0)
result = 31 * result + isValid.hashCode()
return result
}
override fun toString(): String {
return "StringInputField(value='$value', error=$error, isValid=$isValid)"
}
}

View file

@ -0,0 +1,19 @@
package app.k9mail.feature.account.common.ui
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import app.k9mail.core.ui.compose.designsystem.organism.TopAppBar
/**
* Top app bar for the account screens.
*/
@Composable
fun AccountTopAppBar(
title: String,
modifier: Modifier = Modifier,
) {
TopAppBar(
title = title,
modifier = modifier,
)
}

View file

@ -0,0 +1,58 @@
package app.k9mail.feature.account.common.ui
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import app.k9mail.core.ui.compose.designsystem.atom.text.TextDisplayMediumAutoResize
import app.k9mail.core.ui.compose.designsystem.template.ResponsiveWidthContainer
import app.k9mail.core.ui.compose.theme2.MainTheme
private const val TITLE_ICON_SIZE_DP = 56
@Composable
fun AppTitleTopHeader(
title: String,
modifier: Modifier = Modifier,
) {
ResponsiveWidthContainer(
modifier = Modifier
.fillMaxWidth()
.padding(
top = MainTheme.spacings.quadruple,
bottom = MainTheme.spacings.default,
)
.then(modifier),
) { contentPadding ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(
start = MainTheme.spacings.half,
end = MainTheme.spacings.quadruple,
)
.padding(contentPadding)
.then(modifier),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painterResource(id = MainTheme.images.logo),
modifier = Modifier
.padding(all = MainTheme.spacings.default)
.padding(end = MainTheme.spacings.default)
.size(TITLE_ICON_SIZE_DP.dp),
contentDescription = null,
)
TextDisplayMediumAutoResize(text = title)
}
}
}

View file

@ -0,0 +1,42 @@
package app.k9mail.feature.account.common.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import app.k9mail.core.ui.compose.designsystem.template.ResponsiveWidthContainer
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
fun ContentListView(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(),
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(MainTheme.spacings.default),
items: LazyListScope.() -> Unit,
) {
ResponsiveWidthContainer(
modifier = Modifier
.padding(contentPadding)
.fillMaxWidth()
.then(modifier),
) { contentPadding ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.imePadding(),
contentPadding = contentPadding,
horizontalAlignment = horizontalAlignment,
verticalArrangement = verticalArrangement,
) {
items()
}
}
}

View file

@ -0,0 +1,10 @@
package app.k9mail.feature.account.common.ui
import app.k9mail.feature.account.common.domain.entity.InteractionMode
/**
* Interface for screens that can be used in different interaction modes.
*/
interface WithInteractionMode {
val mode: InteractionMode
}

View file

@ -0,0 +1,5 @@
package app.k9mail.feature.account.common.ui
object WizardConstants {
const val CONTINUE_NEXT_DELAY = 500L
}

View file

@ -0,0 +1,71 @@
package app.k9mail.feature.account.common.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonFilled
import app.k9mail.core.ui.compose.designsystem.atom.button.ButtonOutlined
import app.k9mail.core.ui.compose.designsystem.template.ResponsiveWidthContainer
import app.k9mail.core.ui.compose.theme2.MainTheme
import app.k9mail.feature.account.common.R
import net.thunderbird.core.ui.compose.common.modifier.testTagAsResourceId
@Composable
fun WizardNavigationBar(
onNextClick: () -> Unit,
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
nextButtonText: String = stringResource(id = R.string.account_common_button_next),
backButtonText: String = stringResource(id = R.string.account_common_button_back),
state: WizardNavigationBarState = WizardNavigationBarState(),
) {
ResponsiveWidthContainer(
modifier = Modifier
.fillMaxWidth()
.then(modifier),
) { contentPadding ->
Row(
modifier = Modifier
.padding(
start = MainTheme.spacings.quadruple,
top = MainTheme.spacings.default,
end = MainTheme.spacings.quadruple,
bottom = MainTheme.spacings.double,
)
.padding(contentPadding)
.fillMaxWidth(),
horizontalArrangement = getHorizontalArrangement(state),
) {
if (state.showBack) {
ButtonOutlined(
text = backButtonText,
onClick = onBackClick,
enabled = state.isBackEnabled,
modifier = Modifier.testTagAsResourceId("account_setup_back_button"),
)
}
if (state.showNext) {
ButtonFilled(
text = nextButtonText,
onClick = onNextClick,
enabled = state.isNextEnabled,
modifier = Modifier.testTagAsResourceId("account_setup_next_button"),
)
}
}
}
}
private fun getHorizontalArrangement(state: WizardNavigationBarState): Arrangement.Horizontal {
return if (state.showNext && state.showBack) {
Arrangement.SpaceBetween
} else if (state.showNext) {
Arrangement.End
} else {
Arrangement.Start
}
}

View file

@ -0,0 +1,8 @@
package app.k9mail.feature.account.common.ui
data class WizardNavigationBarState(
val isNextEnabled: Boolean = true,
val showNext: Boolean = true,
val isBackEnabled: Boolean = true,
val showBack: Boolean = true,
)

View file

@ -0,0 +1,24 @@
package app.k9mail.feature.account.common.ui.item
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import app.k9mail.core.ui.compose.designsystem.molecule.ErrorView
@Composable
fun LazyItemScope.ErrorItem(
title: String,
modifier: Modifier = Modifier,
message: String? = null,
onRetry: () -> Unit = { },
) {
ListItem(
modifier = modifier,
) {
ErrorView(
title = title,
message = message,
onRetry = onRetry,
)
}
}

View file

@ -0,0 +1,19 @@
package app.k9mail.feature.account.common.ui.item
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import app.k9mail.core.ui.compose.theme2.MainTheme
@Composable
fun defaultHeadlineItemPadding() = PaddingValues(
start = MainTheme.spacings.quadruple,
top = MainTheme.spacings.triple,
end = MainTheme.spacings.quadruple,
bottom = MainTheme.spacings.default,
)
@Composable
fun defaultItemPadding() = PaddingValues(
horizontal = MainTheme.spacings.quadruple,
vertical = MainTheme.spacings.zero,
)

View file

@ -0,0 +1,26 @@
package app.k9mail.feature.account.common.ui.item
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun LazyItemScope.ListItem(
modifier: Modifier = Modifier,
contentPaddingValues: PaddingValues = defaultItemPadding(),
content: @Composable () -> Unit,
) {
Box(
modifier = Modifier
.padding(contentPaddingValues)
.animateItem()
.fillMaxWidth()
.then(modifier),
) {
content()
}
}

View file

@ -0,0 +1,20 @@
package app.k9mail.feature.account.common.ui.item
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import app.k9mail.core.ui.compose.designsystem.molecule.LoadingView
@Composable
fun LazyItemScope.LoadingItem(
modifier: Modifier = Modifier,
message: String? = null,
) {
ListItem(
modifier = modifier,
) {
LoadingView(
message = message,
)
}
}

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>
<string name="account_common_error_server_message">قام الخادم بإرجاع الرسالة التالية:\n%s</string>
<string name="account_common_button_next">التالي</string>
<string name="account_common_button_back">السابق</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>
<string name="account_common_button_next">Növbəti</string>
<string name="account_common_button_back">Geri</string>
<string name="account_common_error_server_message">Server aşağıdakı məlumatı verdi: \n%s</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Далей</string>
<string name="account_common_button_back">Назад</string>
<string name="account_common_error_server_message">Сервер вярнуў наступнае паведамленне:\n%s</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Напред</string>
<string name="account_common_button_back">Назад</string>
<string name="account_common_error_server_message">Сървърът връща следното съобщение\n%s</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_back">পিছনে</string>
<string name="account_common_error_server_message">সার্ভার এই বার্তাটি পাঠিয়েছে:\n%s</string>
<string name="account_common_button_next">পরবর্তী</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Da heul</string>
<string name="account_common_button_back">Kent</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Sljedeći</string>
<string name="account_common_button_back">Nazad</string>
<string name="account_common_error_server_message">Server je odgovorio ovom porukom:
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Següent</string>
<string name="account_common_button_back">Enrere</string>
<string name="account_common_error_server_message">El servidor ha retornat en següent missatge:
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Seguente</string>
<string name="account_common_button_back">Ritornu</string>
<string name="account_common_error_server_message">U servitore hà mandatu quessu messaghju :
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Další</string>
<string name="account_common_button_back">Zpět</string>
<string name="account_common_error_server_message">Server vrátil následující zprávu:
\n%s</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Nesaf</string>
<string name="account_common_button_back">Nôl</string>
<string name="account_common_error_server_message">Dychwelodd y gweinydd y negae ganlynol:\n%s</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Næste</string>
<string name="account_common_button_back">Tilbage</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Weiter</string>
<string name="account_common_button_back">Zurück</string>
<string name="account_common_error_server_message">Der Server hat die folgende Nachricht zurückgegeben:\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Επόμενο</string>
<string name="account_common_button_back">Πίσω</string>
<string name="account_common_error_server_message">Ο διακομιστής επέστρεψε το ακόλουθο μήνυμα:
\n%s</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Next</string>
<string name="account_common_button_back">Back</string>
<string name="account_common_error_server_message">The server returned the following message:\n%s</string>
</resources>

View file

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

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_error_server_message">La servilo sendis la jenan mesaĝon:
\n%s</string>
<string name="account_common_button_next">Sekven</string>
<string name="account_common_button_back">Reen</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Siguiente</string>
<string name="account_common_button_back">Atrás</string>
<string name="account_common_error_server_message">El servidor devolvió el siguiente mensaje:
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Edasi</string>
<string name="account_common_button_back">Tagasi</string>
<string name="account_common_error_server_message">Serveri vastuses sisaldus selline sõnum:
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Hurrengoa</string>
<string name="account_common_button_back">Aurrekoa</string>
<string name="account_common_error_server_message">Zerbitzariak mezu hau itzuli du:
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">بعدی</string>
<string name="account_common_button_back">بازگشت</string>
<string name="account_common_error_server_message">کارساز، پیام زیر را برگردانده است:
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Seuraava</string>
<string name="account_common_button_back">Takaisin</string>
<string name="account_common_error_server_message">Palvelin palautti seuraavan viestin:
\n%s</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Suivant</string>
<string name="account_common_button_back">Précédent</string>
<string name="account_common_error_server_message">Le message suivant a été retourné par le serveur :\n%s</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Folgjende</string>
<string name="account_common_button_back">Tebek</string>
<string name="account_common_error_server_message">De server joech it folgjende berjocht werom:\n%s</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Ar aghaidh</string>
<string name="account_common_button_back">Ar ais</string>
<string name="account_common_error_server_message">Chuir an freastalaí an teachtaireacht seo a leanas ar ais:\n%s</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_error_server_message">Thill am frithealaiche an teachdaireachd a leanas:\n%s</string>
<string name="account_common_button_next">Air adhart</string>
<string name="account_common_button_back">Air ais</string>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">seguinte</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>
<string name="account_common_button_next">अगला</string>
<string name="account_common_button_back">पिछला</string>
<string name="account_common_error_server_message">सेवक ने निम्न संदेश लौटायाः\n%s</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Sljedeće</string>
<string name="account_common_button_back">Natrag</string>
<string name="account_common_error_server_message">Poslužitelj je vratio sljedeću poruku:\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Következő</string>
<string name="account_common_button_back">Vissza</string>
<string name="account_common_error_server_message">A kiszolgáló a következő üzenetet küldte vissza:
\n%s</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>
<string name="account_common_error_server_message">Peladen mengembalikan pesan berikut ini:\n%s</string>
<string name="account_common_button_next">Berikutnya</string>
<string name="account_common_button_back">Kembali</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Næsta</string>
<string name="account_common_button_back">Til baka</string>
<string name="account_common_error_server_message">Póstþjónninn svaraði með eftirfarandi skilaboðum:
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Successivo</string>
<string name="account_common_button_back">Precedente</string>
<string name="account_common_error_server_message">Il server ha restituito il seguente messaggio:
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_error_server_message">השרת החזיר את השגיאה הבאה:
\n%s</string>
<string name="account_common_button_next">הבא</string>
<string name="account_common_button_back">הקודם</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">次へ</string>
<string name="account_common_button_back">戻る</string>
<string name="account_common_error_server_message">サーバーから以下のメッセージが返答されました:
\n%s</string>
</resources>

View file

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

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Uḍfir</string>
<string name="account_common_button_back">Uɣal</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Келесі</string>
<string name="account_common_button_back">Артқа</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">다음</string>
<string name="account_common_button_back">이전</string>
<string name="account_common_error_server_message">서버가 다음의 메시지를 보냈습니다:
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_error_server_message">Serverio grąžintas pranešimas:
\n%s</string>
<string name="account_common_button_next">Toliau</string>
<string name="account_common_button_back">Atgal</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,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Neste</string>
<string name="account_common_button_back">Tilbake</string>
<string name="account_common_error_server_message">Tjeneren svarte følgende:
\n%s</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Volgende</string>
<string name="account_common_button_back">Terug</string>
<string name="account_common_error_server_message">De server gaf het volgende bericht terug: \n%s</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_error_server_message">Serveren returnerte følgjande melding:\n%s</string>
<string name="account_common_button_next">Neste</string>
<string name="account_common_button_back">Tilbake</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Dalej</string>
<string name="account_common_button_back">Cofnij</string>
<string name="account_common_error_server_message">Serwer zwrócił następujący komunikat:
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Avançar</string>
<string name="account_common_button_back">Voltar</string>
<string name="account_common_error_server_message">O servidor retornou a seguinte mensagem:
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Seguinte</string>
<string name="account_common_button_back">Anterior</string>
<string name="account_common_error_server_message">O servidor devolveu a seguinte mensagem:
\n%s</string>
</resources>

View file

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

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Înainte</string>
<string name="account_common_button_back">Înapoi</string>
<string name="account_common_error_server_message">Serverul a returnat următorul mesaj:
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Далее</string>
<string name="account_common_button_back">Назад</string>
<string name="account_common_error_server_message">Сервер вернул следующее сообщение:
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Ďalej</string>
<string name="account_common_button_back">Späť</string>
<string name="account_common_error_server_message">Server vrátil nasledujúcu správu:
\n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Naslednji</string>
<string name="account_common_button_back">Prejšnji</string>
<string name="account_common_error_server_message">Strežnik je vrnil naslednje sporočilo:
\n%s</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Pasuesi</string>
<string name="account_common_button_back">Mbrapsht</string>
<string name="account_common_error_server_message">Shërbyesi ktheu mesazhin vijues: \n%s</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_error_server_message">Сервер је вратио следећу поруку:
\n%s</string>
<string name="account_common_button_next">Следеће</string>
<string name="account_common_button_back">Назад</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="account_common_button_next">Nästa</string>
<string name="account_common_button_back">Tillbaka</string>
<string name="account_common_error_server_message">Servern returnerade följande meddelande:
\n%s</string>
</resources>

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