mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 09:13:23 +00:00
feat(git): New Issue is a full screen; fix square FAB shape
- Converts the New Issue composer from an AlertDialog to a dedicated screen (new Route.GitRepositoryNewIssue + GitNewIssueScreen with its own top bar and a Create action). The Issues FAB now navigates to it. - The extended FAB rendered square because the app theme sets shapes.large (the extended-FAB default shape) to 0.dp; pin an explicit RoundedCornerShape. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
This commit is contained in:
@@ -134,6 +134,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed.FollowPack
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.list.FollowPacksScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.GeoHashPostScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.GeoHashScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitNewIssueScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryCodeScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryIssuesScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryPullsScreen
|
||||
@@ -496,6 +497,7 @@ fun BuildNavigation(
|
||||
composableFromEndArgs<Route.GitRepositoryCode> { GitRepositoryCodeScreen(Address(it.kind, it.pubKeyHex, it.dTag), accountViewModel, nav) }
|
||||
composableFromEndArgs<Route.GitRepositoryIssues> { GitRepositoryIssuesScreen(Address(it.kind, it.pubKeyHex, it.dTag), accountViewModel, nav) }
|
||||
composableFromEndArgs<Route.GitRepositoryPulls> { GitRepositoryPullsScreen(Address(it.kind, it.pubKeyHex, it.dTag), accountViewModel, nav) }
|
||||
composableFromEndArgs<Route.GitRepositoryNewIssue> { GitNewIssueScreen(Address(it.kind, it.pubKeyHex, it.dTag), accountViewModel, nav) }
|
||||
composableFromEndArgs<Route.FollowPack> { FollowPackFeedScreen(Address(it.kind, it.pubKeyHex, it.dTag), accountViewModel, nav) }
|
||||
|
||||
composableFromEndArgs<Route.Room> { ChatroomScreen(it.toKey(), it.message, it.attachment, it.replyId, it.draftId, it.expiresDays, accountViewModel, nav) }
|
||||
|
||||
@@ -559,6 +559,18 @@ sealed class Route {
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable data class GitRepositoryNewIssue(
|
||||
val kind: Int,
|
||||
val pubKeyHex: HexKey,
|
||||
val dTag: String,
|
||||
) : Route() {
|
||||
constructor(address: Address) : this(
|
||||
kind = address.kind,
|
||||
pubKeyHex = address.pubKeyHex,
|
||||
dTag = address.dTag,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable data class FollowPack(
|
||||
val kind: Int,
|
||||
val pubKeyHex: HexKey,
|
||||
|
||||
+91
-55
@@ -22,9 +22,14 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
@@ -33,78 +38,109 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.LocalDisappearingScaffoldPadding
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.ShorterTopAppBar
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.TitleIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip34Git.issue.GitIssueEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.repository.GitRepositoryEvent
|
||||
|
||||
/**
|
||||
* A minimal "New issue" composer: a subject and a markdown body. On submit it
|
||||
* builds a NIP-34 [GitIssueEvent] addressed to [repoNote], signs it with the
|
||||
* account signer and broadcasts it. The repository owner is notified via the `a`
|
||||
* tag the builder adds automatically.
|
||||
*/
|
||||
@Composable
|
||||
fun GitNewIssueDialog(
|
||||
fun GitNewIssueScreen(
|
||||
address: Address,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
LoadAddressableNote(address, accountViewModel) { note ->
|
||||
note?.let { GitNewIssueForm(it, accountViewModel, nav) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Full-screen "New issue" composer: a subject and a markdown body. On submit it builds a
|
||||
* NIP-34 [GitIssueEvent] addressed to [repoNote], signs it with the account signer and
|
||||
* broadcasts it, then returns to the issues list.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun GitNewIssueForm(
|
||||
repoNote: AddressableNote,
|
||||
accountViewModel: AccountViewModel,
|
||||
onDismiss: () -> Unit,
|
||||
nav: INav,
|
||||
) {
|
||||
var subject by rememberSaveable { mutableStateOf("") }
|
||||
var body by rememberSaveable { mutableStateOf("") }
|
||||
var labels by rememberSaveable { mutableStateOf("") }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
icon = { Icon(MaterialSymbols.Description, contentDescription = null, modifier = Modifier.size(24.dp)) },
|
||||
title = { Text(stringRes(R.string.git_new_issue_title)) },
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedTextField(
|
||||
value = subject,
|
||||
onValueChange = { subject = it },
|
||||
label = { Text(stringRes(R.string.git_new_issue_subject)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = body,
|
||||
onValueChange = { body = it },
|
||||
label = { Text(stringRes(R.string.git_new_issue_body)) },
|
||||
minLines = 4,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = labels,
|
||||
onValueChange = { labels = it },
|
||||
label = { Text(stringRes(R.string.git_new_issue_labels)) },
|
||||
placeholder = { Text(stringRes(R.string.git_new_issue_labels_hint)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
enabled = subject.isNotBlank(),
|
||||
onClick = {
|
||||
sendGitIssue(accountViewModel, repoNote, subject.trim(), body.trim(), parseLabels(labels))
|
||||
onDismiss()
|
||||
DisappearingScaffold(
|
||||
isInvertedLayout = false,
|
||||
topBar = {
|
||||
ShorterTopAppBar(
|
||||
title = { Text(stringRes(R.string.git_new_issue_title)) },
|
||||
navigationIcon = {
|
||||
Row(TitleIconModifier, verticalAlignment = Alignment.CenterVertically) {
|
||||
IconButton(onClick = nav::popBack) { ArrowBackIcon() }
|
||||
}
|
||||
},
|
||||
) {
|
||||
Text(stringRes(R.string.git_new_issue_create))
|
||||
}
|
||||
actions = {
|
||||
TextButton(
|
||||
enabled = subject.isNotBlank(),
|
||||
onClick = {
|
||||
sendGitIssue(accountViewModel, repoNote, subject.trim(), body.trim(), parseLabels(labels))
|
||||
nav.popBack()
|
||||
},
|
||||
) {
|
||||
Text(stringRes(R.string.git_new_issue_create))
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) { Text(stringRes(R.string.git_new_issue_cancel)) }
|
||||
},
|
||||
)
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(LocalDisappearingScaffoldPadding.current)
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = subject,
|
||||
onValueChange = { subject = it },
|
||||
label = { Text(stringRes(R.string.git_new_issue_subject)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = body,
|
||||
onValueChange = { body = it },
|
||||
label = { Text(stringRes(R.string.git_new_issue_body)) },
|
||||
minLines = 6,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = labels,
|
||||
onValueChange = { labels = it },
|
||||
label = { Text(stringRes(R.string.git_new_issue_labels)) },
|
||||
placeholder = { Text(stringRes(R.string.git_new_issue_labels_hint)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Splits a free-text label field on commas/whitespace, stripping any leading `#`. */
|
||||
|
||||
+4
-7
@@ -412,8 +412,6 @@ private fun GitRepositoryIssues(
|
||||
val event by observeNoteEvent<GitRepositoryEvent>(note, accountViewModel)
|
||||
RepoContentSubscription(note, event, accountViewModel)
|
||||
|
||||
var showNewIssue by rememberSaveable(note.idHex) { mutableStateOf(false) }
|
||||
|
||||
StatusFeedScreen(
|
||||
persistKey = note.idHex + "GitRepoIssuesStatus",
|
||||
event = event,
|
||||
@@ -422,12 +420,8 @@ private fun GitRepositoryIssues(
|
||||
closedViewModel = closedViewModel,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
floatingButton = if (event != null) ({ NewIssueFab { showNewIssue = true } }) else null,
|
||||
floatingButton = if (event != null) ({ NewIssueFab { nav.nav(Route.GitRepositoryNewIssue(note.address)) } }) else null,
|
||||
)
|
||||
|
||||
if (showNewIssue && event != null) {
|
||||
GitNewIssueDialog(repoNote = note, accountViewModel = accountViewModel, onDismiss = { showNewIssue = false })
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -720,8 +714,11 @@ private fun StatusFilterChips(
|
||||
|
||||
@Composable
|
||||
private fun NewIssueFab(onClick: () -> Unit) {
|
||||
// The app theme overrides shapes.large (the extended-FAB default) to 0.dp, which makes the
|
||||
// FAB square — so pin an explicit rounded shape here.
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = onClick,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
icon = { Icon(MaterialSymbols.Add, contentDescription = null, modifier = Modifier.size(20.dp)) },
|
||||
text = { Text(stringRes(R.string.git_new_issue_button)) },
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user