Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nostrsigner" />
</intent>
</queries>
<application
android:allowBackup="true"
Expand Down Expand Up @@ -105,4 +110,4 @@
</provider>
</application>

</manifest>
</manifest>
2 changes: 2 additions & 0 deletions app/src/main/java/dev/dimension/flare/ui/AppContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import dev.dimension.flare.data.model.AvatarShape
import dev.dimension.flare.data.model.LocalAppearanceSettings
import dev.dimension.flare.data.model.VideoAutoplay
import dev.dimension.flare.data.repository.SettingsRepository
import dev.dimension.flare.ui.common.BindAmberSignerLauncher
import dev.dimension.flare.ui.component.ComponentAppearance
import dev.dimension.flare.ui.component.LocalComponentAppearance
import dev.dimension.flare.ui.screen.home.HomeScreen
Expand All @@ -38,6 +39,7 @@ fun AppContainer(afterInit: () -> Unit) {

@Composable
fun FlareApp(content: @Composable () -> Unit) {
BindAmberSignerLauncher()
val settingsRepository = koinInject<SettingsRepository>()
val appearanceSettings by settingsRepository.appearanceSettings.collectAsState(
AppearanceSettings(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,10 @@
<string name="mastodon_login_verify_message">認証情報を確認するまでしばらくお待ちください。</string>
<string name="nostr_login_title">Nostr アカウントをインポート</string>
<string name="nostr_login_hint">読み取り専用アクセス用に npub または hexpubkey を貼り付けるか、書き込み可能なアカウントをインポートするために nsecを提供します。</string>
<string name="nostr_login_account_hint">npub、nsec、または hex キー</string>
<string name="nostr_login_bunker_hint">bunker:// または nostrconnect:// URI</string>
<string name="nostr_login_bunker_button">bunker に接続</string>
<string name="nostr_login_amber_button">Amber に接続</string>
<string name="nostr_login_generate_button">生成してログイン</string>
<string name="nostr_login_npub_hint">npub または hexpubkey (nsecが設定されている場合はオプション)</string>
<string name="nostr_login_nsec_hint">nsecまたはhex秘密キー</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@

<string name="service_select_welcome_title">Welcome to Flare</string>
<string name="service_select_welcome_message">Please input the server to get started.</string>
<string name="service_select_welcome_hint">Flare supports Mastodon, Misskey, Bluesky and X.</string>
<string name="service_select_welcome_hint">Flare supports Mastodon, Misskey, Bluesky, Nostr and X.</string>
<string name="service_select_welcome_list_hint">Or pick from these servers</string>
<string name="bluesky_login_username_hint">Username</string>
<string name="bluesky_login_password_hint">Password</string>
Expand All @@ -464,6 +464,10 @@
<string name="mastodon_login_verify_message">Please wait while we verify your credentials.</string>
<string name="nostr_login_title">Import Nostr account</string>
<string name="nostr_login_hint">Paste an npub or hex pubkey for read-only access, or provide an nsec to import a writable account.</string>
<string name="nostr_login_account_hint">npub, nsec, or hex key</string>
<string name="nostr_login_bunker_hint">bunker:// or nostrconnect:// URI</string>
<string name="nostr_login_bunker_button">Connect bunker</string>
<string name="nostr_login_amber_button">Connect Amber</string>
<string name="nostr_login_generate_button">Generate and login</string>
<string name="nostr_login_npub_hint">npub or hex pubkey (optional if nsec is set)</string>
<string name="nostr_login_nsec_hint">nsec or hex private key</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,33 @@ import kotlin.native.HiddenFromObjC
public class NostrInputPresenter : PresenterBase<NostrInputPresenter.State>() {
@Immutable
public interface State {
public val secretKey: TextFieldState
public val canLogin: Boolean
public val accountInput: TextFieldState
public val bunkerInput: TextFieldState
public val canLoginAccount: Boolean
public val canLoginBunker: Boolean
}

@Composable
override fun body(): State {
val secretKey = rememberTextFieldState()
val accountInput = rememberTextFieldState()
val bunkerInput = rememberTextFieldState()

val canLogin by remember(secretKey) {
val canLoginAccount by remember(accountInput) {
derivedStateOf {
secretKey.text.isNotEmpty()
accountInput.text.isNotEmpty()
}
}
val canLoginBunker by remember(bunkerInput) {
derivedStateOf {
bunkerInput.text.isNotEmpty()
}
}

return object : State {
override val secretKey: TextFieldState = secretKey
override val canLogin: Boolean = canLogin
override val accountInput: TextFieldState = accountInput
override val bunkerInput: TextFieldState = bunkerInput
override val canLoginAccount: Boolean = canLoginAccount
override val canLoginBunker: Boolean = canLoginBunker
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ import dev.dimension.flare.compose.ui.eula_privacy_policy
import dev.dimension.flare.compose.ui.login_agreement
import dev.dimension.flare.compose.ui.login_button
import dev.dimension.flare.compose.ui.mastodon_login_verify_message
import dev.dimension.flare.compose.ui.nostr_login_nsec_hint
import dev.dimension.flare.compose.ui.nostr_login_account_hint
import dev.dimension.flare.compose.ui.nostr_login_amber_button
import dev.dimension.flare.compose.ui.nostr_login_bunker_button
import dev.dimension.flare.compose.ui.nostr_login_bunker_hint
import dev.dimension.flare.compose.ui.nostr_login_hint
import dev.dimension.flare.compose.ui.service_select_compatibility_warning
import dev.dimension.flare.compose.ui.service_select_empty_message
import dev.dimension.flare.compose.ui.service_select_instance_input_placeholder
Expand Down Expand Up @@ -617,24 +621,29 @@ private fun NostrLoginContent(state: SelectionPresenter.State) {
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
PlatformSecureTextField(
state = state.nostrInputState.secretKey,
PlatformText(
text = stringResource(Res.string.nostr_login_hint),
textAlign = TextAlign.Center,
modifier = Modifier.width(300.dp),
)
PlatformTextField(
state = state.nostrInputState.accountInput,
label = {
PlatformText(text = stringResource(Res.string.nostr_login_nsec_hint))
PlatformText(text = stringResource(Res.string.nostr_login_account_hint))
},
enabled = !state.nostrLoginState.loading,
modifier = Modifier.width(300.dp),
keyboardOptions =
KeyboardOptions(
keyboardType = KeyboardType.Password,
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Done,
autoCorrectEnabled = false,
),
Comment on lines +629 to 641
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Nostr login input now accepts nsec (private key), but it's collected via PlatformTextField with KeyboardType.Text, which will display the secret in clear text. Consider using a secure/password-style field (or masking when input starts with nsec1 / looks like a private key) with an explicit “reveal” toggle to avoid exposing private keys on screen/recordings.

Copilot uses AI. Check for mistakes.
onKeyboardAction = {
if (state.nostrInputState.canLogin) {
if (state.nostrInputState.canLoginAccount) {
state.nostrLoginState.login(
secretKey =
state.nostrInputState.secretKey.text
input =
state.nostrInputState.accountInput.text
.toString(),
)
}
Expand All @@ -643,16 +652,61 @@ private fun NostrLoginContent(state: SelectionPresenter.State) {
PlatformFilledTonalButton(
onClick = {
state.nostrLoginState.login(
secretKey =
state.nostrInputState.secretKey.text
input =
state.nostrInputState.accountInput.text
.toString(),
)
},
modifier = Modifier.width(300.dp),
enabled = state.nostrInputState.canLogin && !state.nostrLoginState.loading,
enabled = state.nostrInputState.canLoginAccount && !state.nostrLoginState.loading,
) {
PlatformText(text = stringResource(Res.string.login_button))
}
PlatformTextField(
state = state.nostrInputState.bunkerInput,
label = {
PlatformText(text = stringResource(Res.string.nostr_login_bunker_hint))
},
enabled = !state.nostrLoginState.loading,
modifier = Modifier.width(300.dp),
keyboardOptions =
KeyboardOptions(
keyboardType = KeyboardType.Uri,
imeAction = ImeAction.Done,
autoCorrectEnabled = false,
),
onKeyboardAction = {
if (state.nostrInputState.canLoginBunker) {
state.nostrLoginState.login(
input =
state.nostrInputState.bunkerInput.text
.toString(),
)
}
},
)
PlatformFilledTonalButton(
onClick = {
state.nostrLoginState.login(
input =
state.nostrInputState.bunkerInput.text
.toString(),
)
},
modifier = Modifier.width(300.dp),
enabled = state.nostrInputState.canLoginBunker && !state.nostrLoginState.loading,
) {
PlatformText(text = stringResource(Res.string.nostr_login_bunker_button))
}
if (state.nostrLoginState.amberAvailable) {
PlatformFilledTonalButton(
onClick = state.nostrLoginState::connectAmber,
modifier = Modifier.width(300.dp),
enabled = !state.nostrLoginState.loading,
) {
PlatformText(text = stringResource(Res.string.nostr_login_amber_button))
}
}
state.nostrLoginState.error?.let {
PlatformText(
text = it.message ?: "Unknown error",
Expand Down
2 changes: 2 additions & 0 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ kotlin {
dependencies {
implementation(libs.core.ktx)
implementation(libs.koin.android)
implementation(libs.koin.compose)
implementation(libs.activity.compose)
}
}
val androidDeviceTest by getting {
Expand Down
Loading
Loading