Skip to content

Commit 595a3d3

Browse files
authored
Merge pull request #349 from jeff-bennett/feature/account-label
feature: add an optional account label to settings.
2 parents 52433b2 + 46eed76 commit 595a3d3

16 files changed

Lines changed: 134 additions & 41 deletions

File tree

app/src/main/java/me/mudkip/moememos/data/model/MemosAccount.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package me.mudkip.moememos.data.model
22

33
import kotlinx.serialization.Serializable
4+
import java.net.URI
45

56
@Serializable
67
data class MemosAccount(
@@ -11,4 +12,19 @@ data class MemosAccount(
1112
val avatarUrl: String = "",
1213
val startDateEpochSecond: Long = 0L,
1314
val defaultVisibility: String = MemoVisibility.PRIVATE.name,
15+
val accountLabel: String = "",
1416
)
17+
18+
fun MemosAccount.displayTitle(): String {
19+
val label = accountLabel.trim()
20+
if (label.isNotEmpty()) return label
21+
val n = name.trim()
22+
if (n.isNotEmpty()) return n
23+
val h = host.trim()
24+
if (h.isEmpty()) return ""
25+
return try {
26+
URI(h).host ?: h
27+
} catch (_: Exception) {
28+
h
29+
}
30+
}

app/src/main/java/me/mudkip/moememos/ui/page/account/MemosAccountPage.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import coil3.network.okhttp.OkHttpNetworkFetcherFactory
3030
import me.mudkip.moememos.R
3131
import me.mudkip.moememos.data.api.MemosProfile
3232
import me.mudkip.moememos.data.model.MemosAccount
33+
import me.mudkip.moememos.data.model.displayTitle
3334
import me.mudkip.moememos.ext.string
3435
import me.mudkip.moememos.ui.component.MemosIcon
3536
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
@@ -47,8 +48,6 @@ fun MemosAccountPage(
4748
onSignOut: () -> Unit
4849
) {
4950
val context = LocalContext.current
50-
val accountHost = account.host.toHttpUrlOrNull()?.host.orEmpty()
51-
val accountName = account.name.ifBlank { accountHost }
5251
val accountAvatarUrl = resolveAvatarUrl(account.host, account.avatarUrl)
5352
val imageLoader = remember(context, okHttpClient) {
5453
ImageLoader.Builder(context)
@@ -91,11 +90,11 @@ fun MemosAccountPage(
9190
}
9291
Column(Modifier.padding(start = 10.dp)) {
9392
Text(
94-
accountName,
93+
account.displayTitle(),
9594
style = MaterialTheme.typography.headlineSmall
9695
)
9796
Text(
98-
accountHost,
97+
account.host,
9998
style = MaterialTheme.typography.titleSmall,
10099
color = MaterialTheme.colorScheme.outline
101100
)

app/src/main/java/me/mudkip/moememos/ui/page/login/LoginPage.kt

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import androidx.compose.foundation.text.KeyboardOptions
1414
import androidx.compose.material.icons.Icons
1515
import androidx.compose.material.icons.automirrored.filled.ArrowBack
1616
import androidx.compose.material.icons.automirrored.outlined.Login
17+
import androidx.compose.material.icons.outlined.Badge
1718
import androidx.compose.material.icons.outlined.Computer
1819
import androidx.compose.material.icons.outlined.PermIdentity
1920
import androidx.compose.material3.BottomAppBar
@@ -75,8 +76,12 @@ fun LoginPage(
7576
val userStateViewModel = LocalUserState.current
7677
val snackbarState = remember { SnackbarHostState() }
7778

79+
var accountLabel by rememberSaveable(stateSaver = TextFieldValue.Saver) {
80+
mutableStateOf(TextFieldValue())
81+
}
82+
7883
var host by rememberSaveable(stateSaver = TextFieldValue.Saver) {
79-
mutableStateOf(TextFieldValue(userStateViewModel.host))
84+
mutableStateOf(TextFieldValue())
8085
}
8186

8287
var accessToken by rememberSaveable(stateSaver = TextFieldValue.Saver) {
@@ -116,6 +121,7 @@ fun LoginPage(
116121
val resp = userStateViewModel.loginMemosWithAccessToken(
117122
host = sanitizedHost,
118123
accessToken = accessToken.text.trim(),
124+
accountLabel = accountLabel.text,
119125
allowHigherV1Version = allowHigherV1Version,
120126
)
121127
resp.suspendOnSuccess {
@@ -226,6 +232,37 @@ fun LoginPage(
226232
}
227233
}
228234
},
235+
value = accountLabel,
236+
onValueChange = { accountLabel = it },
237+
singleLine = true,
238+
leadingIcon = {
239+
Icon(
240+
imageVector = Icons.Outlined.Badge,
241+
contentDescription = R.string.account_name.string
242+
)
243+
},
244+
label = {
245+
Text(R.string.account_name.string)
246+
},
247+
keyboardOptions = KeyboardOptions(
248+
capitalization = KeyboardCapitalization.Words,
249+
autoCorrectEnabled = true,
250+
keyboardType = KeyboardType.Text,
251+
imeAction = ImeAction.Next
252+
)
253+
)
254+
255+
OutlinedTextField(
256+
modifier = Modifier
257+
.fillMaxWidth()
258+
.padding(top = 8.dp)
259+
.onFocusEvent { focusState ->
260+
if (focusState.isFocused) {
261+
coroutineScope.launch {
262+
bringIntoViewRequester.bringIntoView()
263+
}
264+
}
265+
},
229266
value = host,
230267
onValueChange = { host = it },
231268
singleLine = true,
@@ -249,6 +286,7 @@ fun LoginPage(
249286
OutlinedTextField(
250287
modifier = Modifier
251288
.fillMaxWidth()
289+
.padding(top = 8.dp)
252290
.onFocusEvent { focusState ->
253291
if (focusState.isFocused) {
254292
coroutineScope.launch {

app/src/main/java/me/mudkip/moememos/ui/page/settings/SettingItem.kt

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package me.mudkip.moememos.ui.page.settings
22

3+
import androidx.compose.foundation.layout.Column
34
import androidx.compose.foundation.layout.Row
45
import androidx.compose.foundation.layout.Spacer
56
import androidx.compose.foundation.layout.fillMaxWidth
@@ -19,9 +20,11 @@ import androidx.compose.ui.unit.dp
1920
fun SettingItem(
2021
icon: ImageVector,
2122
text: String,
23+
subtitle: String? = null,
2224
trailingIcon: @Composable (() -> Unit)? = null,
2325
onClick: () -> Unit,
2426
) {
27+
val contentDescription = if (subtitle.isNullOrBlank()) text else "$text, $subtitle"
2528
Surface(onClick = onClick) {
2629
Row(
2730
modifier = Modifier
@@ -31,20 +34,27 @@ fun SettingItem(
3134
) {
3235
Icon(
3336
icon,
34-
contentDescription = text,
37+
contentDescription = contentDescription,
3538
modifier = Modifier.padding(start = 8.dp, end = 16.dp),
3639
tint = MaterialTheme.colorScheme.onSurfaceVariant
3740
)
3841
Spacer(modifier = Modifier.width(12.dp))
39-
Text(
40-
text,
41-
style = MaterialTheme.typography.titleMedium,
42-
color = MaterialTheme.colorScheme.onSurface
43-
)
44-
if (trailingIcon != null) {
45-
Spacer(modifier = Modifier.weight(1f))
46-
trailingIcon()
42+
Column(modifier = Modifier.weight(1f)) {
43+
Text(
44+
text,
45+
style = MaterialTheme.typography.titleMedium,
46+
color = MaterialTheme.colorScheme.onSurface
47+
)
48+
if (!subtitle.isNullOrBlank()) {
49+
Text(
50+
subtitle,
51+
style = MaterialTheme.typography.bodySmall,
52+
color = MaterialTheme.colorScheme.outline,
53+
modifier = Modifier.padding(top = 2.dp)
54+
)
55+
}
4756
}
57+
trailingIcon?.invoke()
4858
}
4959
}
50-
}
60+
}

app/src/main/java/me/mudkip/moememos/ui/page/settings/SettingsPage.kt

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import kotlinx.coroutines.launch
4242
import me.mudkip.moememos.R
4343
import me.mudkip.moememos.data.model.Account
4444
import me.mudkip.moememos.data.model.MemoEditGesture
45+
import me.mudkip.moememos.data.model.displayTitle
4546
import me.mudkip.moememos.data.model.Settings
4647
import me.mudkip.moememos.ext.popBackStackIfLifecycleIsResumed
4748
import me.mudkip.moememos.ext.settingsDataStore
@@ -103,27 +104,35 @@ fun SettingsPage(
103104
accounts.forEach { account ->
104105
when (account) {
105106
is Account.MemosV0 -> item {
106-
SettingItem(icon = MemosIcon, text = account.info.name, trailingIcon = {
107-
if (currentAccount?.accountKey() == account.accountKey()) {
108-
Icon(Icons.Outlined.Check,
109-
contentDescription = R.string.selected.string,
110-
modifier = Modifier.padding(start = 16.dp),
111-
tint = MaterialTheme.colorScheme.onSurfaceVariant,
112-
)
113-
}
107+
SettingItem(
108+
icon = MemosIcon,
109+
text = account.info.displayTitle(),
110+
subtitle = account.info.host,
111+
trailingIcon = {
112+
if (currentAccount?.accountKey() == account.accountKey()) {
113+
Icon(Icons.Outlined.Check,
114+
contentDescription = R.string.selected.string,
115+
modifier = Modifier.padding(start = 16.dp),
116+
tint = MaterialTheme.colorScheme.onSurfaceVariant,
117+
)
118+
}
114119
}) {
115120
navController.navigate("${RouteName.ACCOUNT}?accountKey=${account.accountKey()}")
116121
}
117122
}
118123
is Account.MemosV1 -> item {
119-
SettingItem(icon = MemosIcon, text = account.info.name, trailingIcon = {
120-
if (currentAccount?.accountKey() == account.accountKey()) {
121-
Icon(Icons.Outlined.Check,
122-
contentDescription = R.string.selected.string,
123-
modifier = Modifier.padding(start = 16.dp),
124-
tint = MaterialTheme.colorScheme.onSurfaceVariant,
125-
)
126-
}
124+
SettingItem(
125+
icon = MemosIcon,
126+
text = account.info.displayTitle(),
127+
subtitle = account.info.host,
128+
trailingIcon = {
129+
if (currentAccount?.accountKey() == account.accountKey()) {
130+
Icon(Icons.Outlined.Check,
131+
contentDescription = R.string.selected.string,
132+
modifier = Modifier.padding(start = 16.dp),
133+
tint = MaterialTheme.colorScheme.onSurfaceVariant,
134+
)
135+
}
127136
}) {
128137
navController.navigate("${RouteName.ACCOUNT}?accountKey=${account.accountKey()}")
129138
}

app/src/main/java/me/mudkip/moememos/viewmodel/UserStateViewModel.kt

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class UserStateViewModel @Inject constructor(
8585
suspend fun loginMemosWithAccessToken(
8686
host: String,
8787
accessToken: String,
88+
accountLabel: String = "",
8889
allowHigherV1Version: Boolean = false,
8990
): ApiResponse<Unit> = withContext(viewModelScope.coroutineContext) {
9091
try {
@@ -99,30 +100,38 @@ class UserStateViewModel @Inject constructor(
99100
}
100101
}
101102
when (accountCase) {
102-
UserData.AccountCase.MEMOS_V1 -> loginMemosV1WithAccessToken(host, accessToken)
103-
UserData.AccountCase.MEMOS_V0 -> loginMemosV0WithAccessToken(host, accessToken)
103+
UserData.AccountCase.MEMOS_V1 -> loginMemosV1WithAccessToken(host, accessToken, accountLabel)
104+
UserData.AccountCase.MEMOS_V0 -> loginMemosV0WithAccessToken(host, accessToken, accountLabel)
104105
else -> throw MoeMemosException.invalidServer
105106
}
106107
} catch (e: Throwable) {
107108
ApiResponse.exception(e)
108109
}
109110
}
110111

111-
private suspend fun loginMemosV0WithAccessToken(host: String, accessToken: String): ApiResponse<Unit> = withContext(viewModelScope.coroutineContext) {
112+
private suspend fun loginMemosV0WithAccessToken(
113+
host: String,
114+
accessToken: String,
115+
accountLabel: String,
116+
): ApiResponse<Unit> = withContext(viewModelScope.coroutineContext) {
112117
try {
113118
val resp = accountService.createMemosV0Client(host, accessToken).second.me()
114119
if (resp !is ApiResponse.Success) {
115120
return@withContext resp.mapSuccess {}
116121
}
117-
accountService.addAccount(getAccount(host, accessToken, resp.data))
122+
accountService.addAccount(getAccount(host, accessToken, accountLabel, resp.data))
118123
currentUser = resp.data.toUser()
119124
ApiResponse.Success(Unit)
120125
} catch (e: Throwable) {
121126
ApiResponse.exception(e)
122127
}
123128
}
124129

125-
private suspend fun loginMemosV1WithAccessToken(host: String, accessToken: String): ApiResponse<Unit> = withContext(viewModelScope.coroutineContext) {
130+
private suspend fun loginMemosV1WithAccessToken(
131+
host: String,
132+
accessToken: String,
133+
accountLabel: String,
134+
): ApiResponse<Unit> = withContext(viewModelScope.coroutineContext) {
126135
try {
127136
val resp = accountService.createMemosV1Client(host, accessToken).second.getCurrentUser()
128137
if (resp !is ApiResponse.Success) {
@@ -132,7 +141,7 @@ class UserStateViewModel @Inject constructor(
132141
if (user == null) {
133142
return@withContext ApiResponse.exception(MoeMemosException.notLogin)
134143
}
135-
accountService.addAccount(getAccount(host, accessToken, user))
144+
accountService.addAccount(getAccount(host, accessToken, accountLabel, user))
136145
loadCurrentUser().mapSuccess {}
137146
} catch (e: Throwable) {
138147
ApiResponse.exception(e)
@@ -164,7 +173,7 @@ class UserStateViewModel @Inject constructor(
164173
}
165174
}
166175

167-
private fun getAccount(host: String, accessToken: String, user: MemosV0User): Account = Account.MemosV0(
176+
private fun getAccount(host: String, accessToken: String, accountLabel: String, user: MemosV0User): Account = Account.MemosV0(
168177
info = MemosAccount(
169178
host = host,
170179
accessToken = accessToken,
@@ -173,17 +182,19 @@ class UserStateViewModel @Inject constructor(
173182
avatarUrl = user.avatarUrl ?: "",
174183
startDateEpochSecond = user.createdTs,
175184
defaultVisibility = user.toUser().defaultVisibility.name,
185+
accountLabel = accountLabel.trim(),
176186
)
177187
)
178188

179-
private fun getAccount(host: String, accessToken: String, user: MemosV1User): Account = Account.MemosV1(
189+
private fun getAccount(host: String, accessToken: String, accountLabel: String, user: MemosV1User): Account = Account.MemosV1(
180190
info = MemosAccount(
181191
host = host,
182192
accessToken = accessToken,
183193
id = user.name.substringAfterLast('/').toLong(),
184194
name = user.username,
185195
avatarUrl = user.avatarUrl ?: "",
186196
startDateEpochSecond = user.createTime?.epochSecond ?: 0L,
197+
accountLabel = accountLabel.trim(),
187198
)
188199
)
189200
}

app/src/main/res/values-de/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
<string name="sign_in_method">Anmeldemethode</string>
3535
<string name="address">Adresse</string>
3636
<string name="host">Host</string>
37+
<string name="account_name">Kontoname (freiwillig)</string>
3738
<string name="edit">Bearbeiten</string>
3839
<string name="close">Schließen</string>
3940
<string name="add_task">Aufgabe hinzufügen</string>

app/src/main/res/values-fr/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<string name="sign_in_method">Méthode de connexion</string>
3737
<string name="address">Adresse</string>
3838
<string name="host">Hôte</string>
39+
<string name="account_name">Nom du compte (facultatif)</string>
3940
<string name="access_token" translatable="false">Jeton d\'accès</string>
4041
<string name="edit">Modifier</string>
4142
<string name="close">Fermer</string>

app/src/main/res/values-it/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<string name="sign_in_method">Metodo di accesso</string>
3737
<string name="address">Indirizzo</string>
3838
<string name="host">Host</string>
39+
<string name="account_name">Nome account (facoltativo)</string>
3940
<string name="access_token" translatable="false">Token di accesso</string>
4041
<string name="edit">Modifica</string>
4142
<string name="close">Chiudi</string>

app/src/main/res/values-ja/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
<string name="sign_in_method">サインイン方法</string>
3535
<string name="address">アドレス</string>
3636
<string name="host">ホスト</string>
37+
<string name="account_name">アカウント名(任意)</string>
3738
<string name="edit">編集</string>
3839
<string name="close">閉じる</string>
3940
<string name="add_task">タスクを追加</string>

0 commit comments

Comments
 (0)