mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-08-29 07:16:08 +00:00
cleanup
This commit is contained in:
parent
ea0660e717
commit
15d56715b9
@ -14,6 +14,7 @@ import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitchat.android.core.ui.icon.BitChatIcon
|
||||
import kotlinx.coroutines.Job
|
||||
@ -30,6 +31,7 @@ fun BitChatBrandButton(
|
||||
contentDescription: String,
|
||||
modifier: Modifier = Modifier,
|
||||
tint: Color = MaterialTheme.colorScheme.primary,
|
||||
iconSize: Dp = 22.dp,
|
||||
) {
|
||||
var tapCount by remember { mutableIntStateOf(0) }
|
||||
var resetJob by remember { mutableStateOf<Job?>(null) }
|
||||
@ -63,9 +65,7 @@ fun BitChatBrandButton(
|
||||
imageVector = BitChatIcon,
|
||||
contentDescription = contentDescription,
|
||||
tint = tint,
|
||||
// 20.dp rather than 16.dp: the pixel-art bubble has heavy internal negative space,
|
||||
// so it reads noticeably smaller than a solid glyph at the same nominal size.
|
||||
modifier = Modifier.size(20.dp),
|
||||
modifier = Modifier.size(iconSize),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,34 +1,37 @@
|
||||
package com.bitchat.android.core.ui.component.button
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitchat.android.R
|
||||
import com.bitchat.android.ui.theme.LocalBitchatPalette
|
||||
|
||||
@Composable
|
||||
fun CloseButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier.Companion
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val palette = LocalBitchatPalette.current
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier
|
||||
.size(32.dp),
|
||||
// 44.dp to match every other tap target in the app's chrome.
|
||||
modifier = modifier.size(44.dp),
|
||||
colors = IconButtonDefaults.iconButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f),
|
||||
containerColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.1f)
|
||||
contentColor = palette.textSecondary,
|
||||
containerColor = palette.surfaceVariant
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Close,
|
||||
contentDescription = "Close",
|
||||
modifier = Modifier.Companion.size(18.dp)
|
||||
contentDescription = stringResource(R.string.close_plain),
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
package com.bitchat.android.onboarding
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@ -13,9 +16,6 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ColorScheme
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
package com.bitchat.android.onboarding
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
package com.bitchat.android.onboarding
|
||||
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
package com.bitchat.android.onboarding
|
||||
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
|
||||
@ -1,9 +1,5 @@
|
||||
package com.bitchat.android.onboarding
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bluetooth
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
@ -13,6 +9,10 @@ import androidx.compose.material.icons.filled.Mic
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material.icons.filled.Wifi
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
|
||||
@ -1,5 +1,17 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AlternateEmail
|
||||
import androidx.compose.material.icons.filled.DeleteForever
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material.icons.filled.Shuffle
|
||||
import androidx.compose.material.icons.filled.Terminal
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material.icons.filled.WifiOff
|
||||
import androidx.compose.material.icons.outlined.BookmarkBorder
|
||||
import androidx.compose.material.icons.outlined.Lock
|
||||
import androidx.compose.material.icons.outlined.Public
|
||||
import androidx.compose.material.icons.outlined.VisibilityOff
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
@ -19,18 +31,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AlternateEmail
|
||||
import androidx.compose.material.icons.filled.DeleteForever
|
||||
import androidx.compose.material.icons.filled.Groups
|
||||
import androidx.compose.material.icons.filled.Shuffle
|
||||
import androidx.compose.material.icons.filled.Terminal
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material.icons.filled.WifiOff
|
||||
import androidx.compose.material.icons.outlined.BookmarkBorder
|
||||
import androidx.compose.material.icons.outlined.Lock
|
||||
import androidx.compose.material.icons.outlined.Public
|
||||
import androidx.compose.material.icons.outlined.VisibilityOff
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@ -70,10 +70,12 @@ internal val AboutHorizontalPadding = 20.dp
|
||||
/** Card corner radius for grouped rows. */
|
||||
internal val AboutCardShape = RoundedCornerShape(16.dp)
|
||||
|
||||
/** Two top-level views of the sheet. */
|
||||
/**
|
||||
* Two top-level views of the sheet: what the app is and how to drive it, versus the knobs.
|
||||
*/
|
||||
enum class AboutTab {
|
||||
HowToUse,
|
||||
BasicInfo,
|
||||
Info,
|
||||
Settings,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -175,7 +177,7 @@ internal fun AboutTabBar(
|
||||
var rowWidth by remember { mutableStateOf(0.dp) }
|
||||
val tabWidth = rowWidth / 2
|
||||
val indicatorOffset by animateDpAsState(
|
||||
targetValue = if (selected == AboutTab.HowToUse) 0.dp else tabWidth,
|
||||
targetValue = if (selected == AboutTab.Info) 0.dp else tabWidth,
|
||||
animationSpec = tween(BitchatMotion.STANDARD_MS, easing = FastOutSlowInEasing),
|
||||
label = "aboutTabIndicator"
|
||||
)
|
||||
@ -190,15 +192,15 @@ internal fun AboutTabBar(
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
AboutTabLabel(
|
||||
text = stringResource(R.string.about_tab_how_to_use),
|
||||
isSelected = selected == AboutTab.HowToUse,
|
||||
onClick = { onSelect(AboutTab.HowToUse) },
|
||||
text = stringResource(R.string.about_tab_info),
|
||||
isSelected = selected == AboutTab.Info,
|
||||
onClick = { onSelect(AboutTab.Info) },
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
AboutTabLabel(
|
||||
text = stringResource(R.string.about_tab_basic_info),
|
||||
isSelected = selected == AboutTab.BasicInfo,
|
||||
onClick = { onSelect(AboutTab.BasicInfo) },
|
||||
text = stringResource(R.string.about_tab_settings),
|
||||
isSelected = selected == AboutTab.Settings,
|
||||
onClick = { onSelect(AboutTab.Settings) },
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
@ -272,8 +274,8 @@ private fun AboutInstructionRow(
|
||||
contentDescription = null,
|
||||
tint = colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
.size(18.dp)
|
||||
.padding(top = 1.dp)
|
||||
.size(22.dp)
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
@ -315,7 +317,7 @@ internal fun AboutHowToUseSection(modifier: Modifier = Modifier) {
|
||||
text = stringResource(R.string.about_howto_channels)
|
||||
)
|
||||
AboutInstructionRow(
|
||||
icon = Icons.Filled.Groups,
|
||||
icon = Icons.Filled.Person,
|
||||
text = stringResource(R.string.about_howto_people)
|
||||
)
|
||||
AboutInstructionRow(
|
||||
@ -428,7 +430,7 @@ private fun AboutFeatureRow(
|
||||
tint = colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.padding(top = 1.dp)
|
||||
.size(22.dp)
|
||||
.size(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
@ -580,7 +582,7 @@ internal fun SheetHeaderBadge(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = colorScheme.primary,
|
||||
modifier = Modifier.size(24.dp)
|
||||
modifier = Modifier.size(26.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bluetooth
|
||||
import androidx.compose.material.icons.filled.Lock
|
||||
import androidx.compose.material.icons.filled.Public
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material.icons.filled.Speed
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
@ -10,13 +17,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bluetooth
|
||||
import androidx.compose.material.icons.filled.Lock
|
||||
import androidx.compose.material.icons.filled.Public
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material.icons.filled.Speed
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -246,7 +246,7 @@ fun AboutSheet(
|
||||
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val palette = LocalBitchatPalette.current
|
||||
var selectedTab by remember { mutableStateOf(AboutTab.HowToUse) }
|
||||
var selectedTab by remember { mutableStateOf(AboutTab.Info) }
|
||||
|
||||
if (isPresented) {
|
||||
BitchatBottomSheet(
|
||||
@ -273,21 +273,22 @@ fun AboutSheet(
|
||||
)
|
||||
}
|
||||
|
||||
if (selectedTab == AboutTab.HowToUse) {
|
||||
if (selectedTab == AboutTab.Info) {
|
||||
// What the app is, then how to drive it. Both are reference material a
|
||||
// new user reads once, so they belong on the same tab.
|
||||
item(key = "features") {
|
||||
Column {
|
||||
AboutSectionLabel(text = stringResource(R.string.about_section_about))
|
||||
AboutFeatureCard()
|
||||
}
|
||||
}
|
||||
|
||||
item(key = "how_to_use") {
|
||||
AboutHowToUseSection()
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedTab == AboutTab.BasicInfo) {
|
||||
// Features Section - Grouped Card
|
||||
item(key = "features") {
|
||||
Column {
|
||||
AboutSectionLabel(text = stringResource(R.string.about_section_about))
|
||||
AboutFeatureCard()
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedTab == AboutTab.Settings) {
|
||||
// Appearance Section
|
||||
item(key = "appearance") {
|
||||
Column {
|
||||
@ -569,7 +570,7 @@ fun AboutSheet(
|
||||
item(key = "warning") {
|
||||
AboutWarningCard(modifier = Modifier.padding(top = 24.dp))
|
||||
}
|
||||
} // end BasicInfo tab
|
||||
} // end Settings tab
|
||||
|
||||
// Footer
|
||||
item(key = "footer") {
|
||||
|
||||
112
app/src/main/java/com/bitchat/android/ui/AnimatedCount.kt
Normal file
112
app/src/main/java/com/bitchat/android/ui/AnimatedCount.kt
Normal file
@ -0,0 +1,112 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.SizeTransform
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import com.bitchat.android.ui.theme.BitchatMotion
|
||||
|
||||
/**
|
||||
* Counts that roll to their new value instead of snapping.
|
||||
*
|
||||
* Peer counts change on their own, without the user doing anything, so a hard digit swap is easy
|
||||
* to miss and looks like a rendering fault when it is noticed. Sliding the digits in the
|
||||
* direction the number moved — up when someone joins, down when someone leaves — conveys the
|
||||
* change without needing a separate indicator.
|
||||
*/
|
||||
@Composable
|
||||
fun AnimatedCount(
|
||||
count: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
color: Color = Color.Unspecified,
|
||||
fontSize: TextUnit = TextUnit.Unspecified,
|
||||
fontWeight: FontWeight? = null,
|
||||
fontFamily: FontFamily? = null,
|
||||
prefix: String = "",
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = count,
|
||||
transitionSpec = {
|
||||
val goingUp = targetState > initialState
|
||||
(
|
||||
slideInVertically(
|
||||
animationSpec = tween(BitchatMotion.STANDARD_MS, easing = FastOutSlowInEasing),
|
||||
initialOffsetY = { height -> if (goingUp) height else -height }
|
||||
) + fadeIn(tween(BitchatMotion.STANDARD_MS))
|
||||
).togetherWith(
|
||||
slideOutVertically(
|
||||
animationSpec = tween(BitchatMotion.STANDARD_MS, easing = FastOutSlowInEasing),
|
||||
targetOffsetY = { height -> if (goingUp) -height else height }
|
||||
) + fadeOut(tween(BitchatMotion.QUICK_MS))
|
||||
// Clip so the outgoing digit cannot bleed past the text bounds mid-transition.
|
||||
) using SizeTransform(clip = true)
|
||||
},
|
||||
modifier = modifier,
|
||||
label = "animatedCount"
|
||||
) { value ->
|
||||
Text(
|
||||
text = "$prefix$value",
|
||||
style = style,
|
||||
color = color,
|
||||
fontSize = fontSize,
|
||||
fontWeight = fontWeight,
|
||||
fontFamily = fontFamily,
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cross-fades a label whose text embeds a count, e.g. `People (7)` or `3 people`.
|
||||
*
|
||||
* Used where the number is not isolated in its own composable and cannot be rolled on its own.
|
||||
* The transition is keyed on [count] rather than on [text] so that a label changing for some
|
||||
* other reason (a locale switch, say) does not animate.
|
||||
*/
|
||||
@Composable
|
||||
fun AnimatedCountLabel(
|
||||
count: Int,
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
style: TextStyle = LocalTextStyle.current,
|
||||
color: Color = Color.Unspecified,
|
||||
fontSize: TextUnit = TextUnit.Unspecified,
|
||||
fontWeight: FontWeight? = null,
|
||||
fontFamily: FontFamily? = null,
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = count,
|
||||
transitionSpec = {
|
||||
fadeIn(tween(BitchatMotion.STANDARD_MS, easing = FastOutSlowInEasing))
|
||||
.togetherWith(fadeOut(tween(BitchatMotion.QUICK_MS)))
|
||||
.using(SizeTransform(clip = false))
|
||||
},
|
||||
modifier = modifier,
|
||||
label = "animatedCountLabel"
|
||||
) { _ ->
|
||||
Text(
|
||||
text = text,
|
||||
style = style,
|
||||
color = color,
|
||||
fontSize = fontSize,
|
||||
fontWeight = fontWeight,
|
||||
fontFamily = fontFamily,
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,20 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.*
|
||||
@ -14,10 +24,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -45,14 +51,29 @@ import com.bitchat.android.ui.theme.LocalBitchatPalette
|
||||
* Extracted from ChatScreen.kt for better organization
|
||||
*/
|
||||
|
||||
/** Height of the chat top bar. Taller than the old 42.dp so 40.dp tap targets fit properly. */
|
||||
val ChatHeaderHeight = 48.dp
|
||||
/** Height of the chat top bar. Taller than the old 42.dp so 44.dp tap targets fit properly. */
|
||||
val ChatHeaderHeight = 52.dp
|
||||
|
||||
/** Standard icon size in the header. */
|
||||
private val HeaderIconSize = 18.dp
|
||||
/**
|
||||
* The single icon size used by every glyph in the top bar.
|
||||
*
|
||||
* Previously the bar mixed 12/14/16/18.dp icons, which is why it read as a collection of
|
||||
* unrelated glyphs rather than one control strip. One size for everything, and large enough to
|
||||
* actually see.
|
||||
*/
|
||||
internal val HeaderIconSize = 22.dp
|
||||
|
||||
/**
|
||||
* Text size for the top bar's labels: nickname, channel name, peer count.
|
||||
*
|
||||
* A step up from the 15.sp body scale. The bar is the app's primary status readout and was
|
||||
* noticeably harder to read than the messages below it; the extra point costs nothing because
|
||||
* the bar's height is driven by [HeaderTapTarget], not by the text.
|
||||
*/
|
||||
private val HeaderTextSize = 17.sp
|
||||
|
||||
/** Minimum tap target for every interactive element in the header. */
|
||||
private val HeaderTapTarget = 40.dp
|
||||
private val HeaderTapTarget = 44.dp
|
||||
|
||||
/** Corner radius for the header's tappable label+icon clusters. */
|
||||
private val HeaderClusterShape = RoundedCornerShape(8.dp)
|
||||
@ -81,6 +102,14 @@ private fun HeaderIconButton(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection status indicator.
|
||||
*
|
||||
* Only visible while the connection is still coming up (or has failed). A steady green "all
|
||||
* good" light is noise: the absence of the indicator already means everything is fine, and the
|
||||
* eye stops registering an always-on dot anyway. It fades and scales in/out so appearing and
|
||||
* disappearing reads as a state change rather than a glitch.
|
||||
*/
|
||||
@Composable
|
||||
fun TorStatusDot(
|
||||
modifier: Modifier = Modifier
|
||||
@ -88,27 +117,35 @@ fun TorStatusDot(
|
||||
val palette = LocalBitchatPalette.current
|
||||
val torProvider = remember { com.bitchat.android.net.ArtiTorManager.getInstance() }
|
||||
val torStatus by torProvider.statusFlow.collectAsState()
|
||||
|
||||
if (torStatus.mode != com.bitchat.android.net.TorMode.OFF) {
|
||||
val targetColor = when {
|
||||
torStatus.running && torStatus.bootstrapPercent < 100 -> palette.accentOrange
|
||||
torStatus.running && torStatus.bootstrapPercent >= 100 -> palette.accentGreen
|
||||
else -> palette.accentRed
|
||||
}
|
||||
// Cross-fade rather than snap: Tor flips through bootstrapping states frequently and a
|
||||
// hard colour cut in the corner of the screen reads as a glitch.
|
||||
|
||||
val isEnabled = torStatus.mode != com.bitchat.android.net.TorMode.OFF
|
||||
val isEstablished = torStatus.running && torStatus.bootstrapPercent >= 100
|
||||
val isVisible = isEnabled && !isEstablished
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = isVisible,
|
||||
enter = fadeIn(tween(BitchatMotion.STANDARD_MS)) +
|
||||
scaleIn(
|
||||
initialScale = 0.4f,
|
||||
animationSpec = tween(BitchatMotion.STANDARD_MS, easing = FastOutSlowInEasing)
|
||||
),
|
||||
exit = fadeOut(tween(BitchatMotion.QUICK_MS)) +
|
||||
scaleOut(
|
||||
targetScale = 0.4f,
|
||||
animationSpec = tween(BitchatMotion.QUICK_MS, easing = FastOutSlowInEasing)
|
||||
),
|
||||
) {
|
||||
// Bootstrapping vs. failed. Cross-faded, because Tor flips between these frequently and
|
||||
// a hard colour cut in the corner of the screen reads as a rendering fault.
|
||||
val dotColor by animateColorAsState(
|
||||
targetValue = targetColor,
|
||||
targetValue = if (torStatus.running) palette.accentOrange else palette.accentRed,
|
||||
animationSpec = tween(BitchatMotion.STANDARD_MS, easing = FastOutSlowInEasing),
|
||||
label = "torDotColor"
|
||||
)
|
||||
Canvas(
|
||||
modifier = modifier
|
||||
) {
|
||||
val radius = size.minDimension / 2
|
||||
Canvas(modifier = modifier) {
|
||||
drawCircle(
|
||||
color = dotColor,
|
||||
radius = radius,
|
||||
radius = size.minDimension / 2,
|
||||
center = Offset(size.width / 2, size.height / 2)
|
||||
)
|
||||
}
|
||||
@ -179,15 +216,17 @@ fun NicknameEditor(
|
||||
Text(
|
||||
text = stringResource(R.string.at_symbol),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontSize = HeaderTextSize,
|
||||
color = colorScheme.primary.copy(alpha = 0.7f)
|
||||
)
|
||||
|
||||
|
||||
BasicTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
textStyle = MaterialTheme.typography.bodyMedium.copy(
|
||||
color = colorScheme.primary,
|
||||
fontFamily = FontFamily.Monospace
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontSize = HeaderTextSize
|
||||
),
|
||||
cursorBrush = SolidColor(colorScheme.primary),
|
||||
singleLine = true,
|
||||
@ -198,7 +237,7 @@ fun NicknameEditor(
|
||||
}
|
||||
),
|
||||
modifier = Modifier
|
||||
.widthIn(max = 140.dp)
|
||||
.widthIn(max = 150.dp)
|
||||
.horizontalScroll(scrollState)
|
||||
)
|
||||
}
|
||||
@ -250,9 +289,9 @@ fun PeerCounter(
|
||||
.padding(horizontal = 6.dp)
|
||||
) {
|
||||
Icon(
|
||||
// `Groups` reads as a crowd at 18.dp, where `Group`'s two-person silhouette turns
|
||||
// into an indistinct blob.
|
||||
imageVector = Icons.Filled.Groups,
|
||||
// A single silhouette rather than a crowd: at 22.dp a multi-person glyph collapses
|
||||
// into an indistinct blob, and the number beside it already conveys "how many".
|
||||
imageVector = Icons.Filled.Person,
|
||||
contentDescription = when (selectedLocationChannel) {
|
||||
is com.bitchat.android.geohash.ChannelID.Location -> stringResource(R.string.cd_geohash_participants)
|
||||
else -> stringResource(R.string.cd_connected_peers)
|
||||
@ -261,17 +300,20 @@ fun PeerCounter(
|
||||
tint = animatedCountColor
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "$peopleCount",
|
||||
AnimatedCount(
|
||||
count = peopleCount,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontSize = HeaderTextSize,
|
||||
color = animatedCountColor,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
|
||||
|
||||
if (joinedChannels.isNotEmpty()) {
|
||||
Text(
|
||||
text = stringResource(R.string.channel_count_prefix) + "${joinedChannels.size}",
|
||||
AnimatedCount(
|
||||
count = joinedChannels.size,
|
||||
prefix = stringResource(R.string.channel_count_prefix),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontSize = HeaderTextSize,
|
||||
color = if (isConnected) palette.accentGreen else palette.accentRed,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
@ -343,7 +385,7 @@ private fun ChannelHeader(
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.back),
|
||||
modifier = Modifier.size(20.dp),
|
||||
modifier = Modifier.size(HeaderIconSize),
|
||||
tint = colorScheme.primary
|
||||
)
|
||||
}
|
||||
@ -352,6 +394,7 @@ private fun ChannelHeader(
|
||||
Text(
|
||||
text = "#$channel",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontSize = HeaderTextSize,
|
||||
color = colorScheme.primary,
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
@ -368,6 +411,7 @@ private fun ChannelHeader(
|
||||
Text(
|
||||
text = stringResource(R.string.chat_leave),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontSize = 15.sp,
|
||||
color = palette.accentRed
|
||||
)
|
||||
}
|
||||
@ -395,11 +439,6 @@ private fun MainHeader(
|
||||
val selectedLocationChannel by viewModel.selectedLocationChannel.collectAsStateWithLifecycle()
|
||||
val geohashPeople by viewModel.geohashPeople.collectAsStateWithLifecycle()
|
||||
|
||||
// Bookmarks store for current geohash toggle (iOS parity)
|
||||
val context = androidx.compose.ui.platform.LocalContext.current
|
||||
val bookmarksStore = remember { com.bitchat.android.geohash.GeohashBookmarksStore.getInstance(context) }
|
||||
val bookmarks by bookmarksStore.bookmarks.collectAsStateWithLifecycle()
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
@ -423,6 +462,7 @@ private fun MainHeader(
|
||||
Text(
|
||||
text = "/",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontSize = HeaderTextSize,
|
||||
// Dimmed: the slash is a separator, not content. At full brightness it competed
|
||||
// with the nickname beside it.
|
||||
color = colorScheme.primary.copy(alpha = 0.45f),
|
||||
@ -435,12 +475,26 @@ private fun MainHeader(
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Status cluster. Order matches the design: bookmark, channel, people.
|
||||
// MARK: - Status cluster.
|
||||
//
|
||||
// Order, left to right: connection state, unread DMs, notes, channel, people.
|
||||
// The connection indicator leads because it is the only item that can invalidate
|
||||
// everything to its right, and because it appears and disappears on its own — a fixed
|
||||
// leftmost slot means the rest of the cluster never shifts when it does.
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
// Tight, because every child below is its own >=40.dp tap target.
|
||||
// Tight, because every child below is its own >=44.dp tap target.
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
// Connection status. The Box holds the slot open at a constant width so the icons to
|
||||
// its right stay put whether or not the dot is currently showing.
|
||||
Box(
|
||||
modifier = Modifier.size(HeaderIconSize),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
TorStatusDot(modifier = Modifier.size(8.dp))
|
||||
}
|
||||
|
||||
// Unread private messages badge (click to open most recent DM)
|
||||
if (hasUnreadPrivateMessages.isNotEmpty()) {
|
||||
HeaderIconButton(
|
||||
@ -462,37 +516,9 @@ private fun MainHeader(
|
||||
onClick = onLocationNotesClick
|
||||
)
|
||||
|
||||
// Tor status dot. Wrapped in a fixed box so its optical position no longer depends
|
||||
// on ad-hoc asymmetric padding.
|
||||
Box(modifier = Modifier.size(16.dp), contentAlignment = Alignment.Center) {
|
||||
TorStatusDot(modifier = Modifier.size(6.dp))
|
||||
}
|
||||
|
||||
// PoW status indicator
|
||||
PoWStatusIndicator(
|
||||
modifier = Modifier,
|
||||
style = PoWIndicatorStyle.COMPACT
|
||||
)
|
||||
|
||||
// Bookmark toggle for current geohash (not shown for mesh)
|
||||
val currentGeohash: String? = when (val sc = selectedLocationChannel) {
|
||||
is com.bitchat.android.geohash.ChannelID.Location -> sc.channel.geohash
|
||||
else -> null
|
||||
}
|
||||
if (currentGeohash != null) {
|
||||
val isBookmarked = bookmarks.contains(currentGeohash)
|
||||
HeaderIconButton(
|
||||
onClick = { bookmarksStore.toggle(currentGeohash) },
|
||||
contentDescription = stringResource(R.string.cd_toggle_bookmark)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (isBookmarked) Icons.Filled.Bookmark else Icons.Outlined.BookmarkBorder,
|
||||
contentDescription = stringResource(R.string.cd_toggle_bookmark),
|
||||
tint = if (isBookmarked) palette.accentGreen else palette.textSecondary,
|
||||
modifier = Modifier.size(HeaderIconSize)
|
||||
)
|
||||
}
|
||||
}
|
||||
// Bookmarking lives in the Location Channels sheet, one tap away via the channel
|
||||
// button below. Duplicating it here bought a shortcut for a rare action at the cost
|
||||
// of a slot in the app's most crowded row.
|
||||
|
||||
LocationChannelsButton(
|
||||
viewModel = viewModel,
|
||||
@ -532,8 +558,11 @@ private fun LocationChannelsButton(
|
||||
|
||||
val isLocation = selectedChannel is com.bitchat.android.geohash.ChannelID.Location
|
||||
val badgeText = when (val channel = selectedChannel) {
|
||||
// Geohashes keep the '#' because that is how they are written and typed everywhere else.
|
||||
is com.bitchat.android.geohash.ChannelID.Location -> "#${channel.channel.geohash}"
|
||||
else -> "#mesh"
|
||||
// The local mesh is not a hashtag channel, and the Bluetooth glyph already says what it
|
||||
// is, so it is plain "mesh".
|
||||
else -> stringResource(R.string.mesh_label)
|
||||
}
|
||||
val badgeColor = if (isLocation) palette.accentGreen else palette.accentBlue
|
||||
val badgeIcon = if (isLocation) Icons.Outlined.Public else Icons.Filled.Bluetooth
|
||||
@ -557,6 +586,7 @@ private fun LocationChannelsButton(
|
||||
Text(
|
||||
text = badgeText,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontSize = HeaderTextSize,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = badgeColor,
|
||||
maxLines = 1
|
||||
@ -567,7 +597,7 @@ private fun LocationChannelsButton(
|
||||
Icon(
|
||||
imageVector = Icons.Default.PinDrop,
|
||||
contentDescription = stringResource(R.string.cd_teleported),
|
||||
modifier = Modifier.size(12.dp),
|
||||
modifier = Modifier.size(HeaderIconSize),
|
||||
tint = badgeColor
|
||||
)
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@ package com.bitchat.android.ui
|
||||
// [Goose] Installing FileShareDispatcher handler in ChatScreen to forward file sends to ViewModel
|
||||
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDownward
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
@ -14,8 +16,6 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDownward
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.IconButton
|
||||
@ -303,6 +303,7 @@ fun ChatScreen(viewModel: ChatViewModel) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowDownward,
|
||||
contentDescription = stringResource(com.bitchat.android.R.string.cd_scroll_to_bottom),
|
||||
modifier = Modifier.size(22.dp),
|
||||
tint = palette.accentGreen
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.Explore
|
||||
import androidx.compose.material.icons.outlined.LocationOn
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -193,7 +193,7 @@ private fun GeohashPersonItem(
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Email,
|
||||
contentDescription = stringResource(R.string.cd_unread_message),
|
||||
modifier = Modifier.size(16.dp),
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = palette.accentOrange
|
||||
)
|
||||
} else {
|
||||
@ -205,7 +205,7 @@ private fun GeohashPersonItem(
|
||||
else -> "face.smiling" to palette.textSecondary
|
||||
}
|
||||
|
||||
// Use appropriate Material icon (closest match to iOS SF Symbols)
|
||||
// Teleported users get the pin (they chose this location); locals get the person.
|
||||
val icon = when (iconName) {
|
||||
"face.dashed" -> Icons.Outlined.Explore
|
||||
else -> Icons.Outlined.LocationOn
|
||||
@ -214,8 +214,8 @@ private fun GeohashPersonItem(
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = if (isTeleported || isMyTeleported) "Teleported user" else "User",
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = iconColor.copy(alpha = if (iconName == "face.dashed") 0.6f else 1.0f) // Make dashed faces slightly transparent
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = iconColor.copy(alpha = if (iconName == "face.dashed") 0.6f else 1.0f)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Remove
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
@ -15,10 +19,6 @@ import android.webkit.WebViewClient
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Remove
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
|
||||
@ -210,22 +210,11 @@ class GeohashViewModel(
|
||||
powDifficulty = if (pow.enabled) pow.difficulty else null
|
||||
)
|
||||
messageManager.addChannelMessage("geo:${channel.geohash}", localMsg)
|
||||
val startedMining = pow.enabled && pow.difficulty > 0
|
||||
if (startedMining) {
|
||||
com.bitchat.android.ui.PoWMiningTracker.startMiningMessage(tempId)
|
||||
}
|
||||
try {
|
||||
val identity = NostrIdentityBridge.deriveIdentity(forGeohash = channel.geohash, context = getApplication())
|
||||
val teleported = state.isTeleported.value
|
||||
val event = NostrProtocol.createEphemeralGeohashEvent(content, channel.geohash, identity, nickname, teleported)
|
||||
val relayManager = NostrRelayManager.getInstance(getApplication())
|
||||
relayManager.sendEventToGeohash(event, channel.geohash, includeDefaults = false, nRelays = 5)
|
||||
} finally {
|
||||
// Ensure we stop the per-message mining animation regardless of success/failure
|
||||
if (startedMining) {
|
||||
com.bitchat.android.ui.PoWMiningTracker.stopMiningMessage(tempId)
|
||||
}
|
||||
}
|
||||
val identity = NostrIdentityBridge.deriveIdentity(forGeohash = channel.geohash, context = getApplication())
|
||||
val teleported = state.isTeleported.value
|
||||
val event = NostrProtocol.createEphemeralGeohashEvent(content, channel.geohash, identity, nickname, teleported)
|
||||
val relayManager = NostrRelayManager.getInstance(getApplication())
|
||||
relayManager.sendEventToGeohash(event, channel.geohash, includeDefaults = false, nRelays = 5)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to send geohash message: ${e.message}")
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@ package com.bitchat.android.ui
|
||||
// [Goose] TODO: Replace inline file attachment stub with FilePickerButton abstraction that dispatches via FileShareDispatcher
|
||||
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
@ -12,8 +14,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -187,10 +187,10 @@ private val ComposerMinHeight = 44.dp
|
||||
private val ComposerShape = RoundedCornerShape(22.dp)
|
||||
|
||||
/** Diameter of the circular send affordance nested inside the pill. */
|
||||
private val SendButtonSize = 32.dp
|
||||
private val SendButtonSize = 36.dp
|
||||
|
||||
/** Tap target for the media affordances sitting outside the pill. */
|
||||
private val MediaButtonSize = 40.dp
|
||||
/** Icon size shared by the composer's glyphs, matching the top bar. */
|
||||
internal val ComposerIconSize = 22.dp
|
||||
|
||||
@Composable
|
||||
fun MessageInput(
|
||||
@ -325,7 +325,7 @@ fun MessageInput(
|
||||
hasText = hasText,
|
||||
isAccented = selectedPrivatePeer != null || currentChannel != null,
|
||||
onSend = onSend,
|
||||
modifier = Modifier.padding(end = 6.dp, bottom = 6.dp)
|
||||
modifier = Modifier.padding(end = 4.dp, bottom = 4.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@ -430,7 +430,7 @@ private fun SendButton(
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowUpward,
|
||||
contentDescription = stringResource(id = R.string.send_message),
|
||||
modifier = Modifier.size(18.dp),
|
||||
modifier = Modifier.size(ComposerIconSize),
|
||||
tint = tint
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Link
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.background
|
||||
@ -7,8 +9,6 @@ import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Link
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bookmark
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Map
|
||||
import androidx.compose.material.icons.filled.PinDrop
|
||||
import androidx.compose.material.icons.outlined.BookmarkBorder
|
||||
import androidx.compose.material.icons.outlined.Public
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
@ -15,13 +22,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bookmark
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Map
|
||||
import androidx.compose.material.icons.filled.PinDrop
|
||||
import androidx.compose.material.icons.outlined.BookmarkBorder
|
||||
import androidx.compose.material.icons.outlined.Public
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -218,6 +218,7 @@ fun LocationChannelsSheet(
|
||||
title = meshTitleWithCount(viewModel),
|
||||
subtitle = stringResource(R.string.location_bluetooth_subtitle, bluetoothRangeString()),
|
||||
isSelected = selectedChannel is ChannelID.Mesh,
|
||||
participantCount = meshCount(viewModel),
|
||||
titleColor = standardBlue,
|
||||
titleBold = meshCount(viewModel) > 0,
|
||||
trailingContent = null,
|
||||
@ -251,6 +252,7 @@ fun LocationChannelsSheet(
|
||||
title = geohashTitleWithCount(channel, participantCount),
|
||||
subtitle = subtitlePrefix + (namePart?.let { " • $it" } ?: ""),
|
||||
isSelected = isChannelSelected(channel, selectedChannel),
|
||||
participantCount = participantCount,
|
||||
titleColor = standardGreen,
|
||||
titleBold = highlight,
|
||||
trailingContent = {
|
||||
@ -259,7 +261,7 @@ fun LocationChannelsSheet(
|
||||
imageVector = if (isBookmarked) Icons.Filled.Bookmark else Icons.Outlined.BookmarkBorder,
|
||||
contentDescription = if (isBookmarked) stringResource(R.string.cd_remove_bookmark) else stringResource(R.string.cd_add_bookmark),
|
||||
tint = if (isBookmarked) palette.accentGreen else palette.textSecondary,
|
||||
modifier = Modifier.size(18.dp)
|
||||
modifier = Modifier.size(22.dp)
|
||||
)
|
||||
}
|
||||
},
|
||||
@ -314,6 +316,7 @@ fun LocationChannelsSheet(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
isSelected = isChannelSelected(channel, selectedChannel),
|
||||
participantCount = participantCount,
|
||||
titleColor = null,
|
||||
titleBold = participantCount > 0,
|
||||
trailingContent = {
|
||||
@ -322,7 +325,7 @@ fun LocationChannelsSheet(
|
||||
imageVector = Icons.Filled.Bookmark,
|
||||
contentDescription = stringResource(R.string.cd_remove_bookmark),
|
||||
tint = palette.accentGreen,
|
||||
modifier = Modifier.size(18.dp)
|
||||
modifier = Modifier.size(22.dp)
|
||||
)
|
||||
}
|
||||
},
|
||||
@ -432,7 +435,7 @@ fun LocationChannelsSheet(
|
||||
imageVector = Icons.Filled.Map,
|
||||
contentDescription = stringResource(R.string.cd_open_map),
|
||||
tint = palette.textSecondary,
|
||||
modifier = Modifier.size(18.dp)
|
||||
modifier = Modifier.size(22.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@ -468,7 +471,7 @@ fun LocationChannelsSheet(
|
||||
Icon(
|
||||
imageVector = Icons.Filled.PinDrop,
|
||||
contentDescription = stringResource(R.string.cd_teleport),
|
||||
modifier = Modifier.size(12.dp),
|
||||
modifier = Modifier.size(14.dp),
|
||||
tint = teleportColor
|
||||
)
|
||||
Text(
|
||||
@ -641,6 +644,8 @@ private fun ChannelRow(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
isSelected: Boolean,
|
||||
/** Drives the count animation; the number itself is already baked into [title]. */
|
||||
participantCount: Int,
|
||||
titleColor: Color? = null,
|
||||
titleBold: Boolean = false,
|
||||
trailingContent: (@Composable (() -> Unit))? = null,
|
||||
@ -701,7 +706,10 @@ private fun ChannelRow(
|
||||
)
|
||||
|
||||
countSuffix?.let { count ->
|
||||
Text(
|
||||
AnimatedCountLabel(
|
||||
// Participant counts are polled from the relays, so they change
|
||||
// while the sheet is open.
|
||||
count = participantCount,
|
||||
text = count,
|
||||
fontSize = 11.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Description
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@ -55,12 +55,12 @@ fun LocationNotesButton(
|
||||
val hasNotes = notesCount > 0
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier.size(40.dp)
|
||||
modifier = modifier.size(44.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Description, // "long.text.page.and.pencil" equivalent
|
||||
imageVector = Icons.Outlined.Description,
|
||||
contentDescription = stringResource(R.string.cd_location_notes),
|
||||
modifier = Modifier.size(18.dp),
|
||||
modifier = Modifier.size(HeaderIconSize),
|
||||
tint = if (hasNotes) colorScheme.primary else LocalBitchatPalette.current.textSecondary
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowUpward
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
@ -9,8 +11,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowUpward
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.getValue
|
||||
|
||||
@ -1,183 +0,0 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bitchat.android.mesh.MeshService
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.text.SimpleDateFormat
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* Animation state for individual characters
|
||||
*/
|
||||
private enum class CharacterAnimationState {
|
||||
ENCRYPTED, // Showing random encrypted characters
|
||||
FINAL // Showing final decrypted character
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a message should be animated based on its mining state
|
||||
*/
|
||||
@Composable
|
||||
fun shouldAnimateMessage(messageId: String): Boolean {
|
||||
val miningMessages by PoWMiningTracker.miningMessages.collectAsStateWithLifecycle()
|
||||
return miningMessages.contains(messageId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks which messages are currently being mined for PoW
|
||||
* Provides reactive state for UI animations
|
||||
*/
|
||||
object PoWMiningTracker {
|
||||
private val _miningMessages = MutableStateFlow<Set<String>>(emptySet())
|
||||
val miningMessages: StateFlow<Set<String>> = _miningMessages.asStateFlow()
|
||||
|
||||
/**
|
||||
* Start tracking a message as mining
|
||||
*/
|
||||
fun startMiningMessage(messageId: String) {
|
||||
_miningMessages.value = _miningMessages.value + messageId
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop tracking a message as mining
|
||||
*/
|
||||
fun stopMiningMessage(messageId: String) {
|
||||
_miningMessages.value = _miningMessages.value - messageId
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a message is currently mining
|
||||
*/
|
||||
fun isMiningMessage(messageId: String): Boolean {
|
||||
return _miningMessages.value.contains(messageId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all mining messages (for cleanup)
|
||||
*/
|
||||
fun clearAllMining() {
|
||||
_miningMessages.value = emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the active PoW animation inside the same two-row layout used by static text messages.
|
||||
*/
|
||||
@Composable
|
||||
fun MessageWithMatrixAnimation(
|
||||
message: BitchatMessage,
|
||||
currentUserNickname: String,
|
||||
meshService: MeshService,
|
||||
colorScheme: ColorScheme,
|
||||
timeFormatter: SimpleDateFormat,
|
||||
onNicknameClick: ((String) -> Unit)?,
|
||||
onMessageLongPress: ((BitchatMessage) -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
showSender: Boolean = true,
|
||||
) {
|
||||
AnimatedMessageDisplay(
|
||||
message = message,
|
||||
currentUserNickname = currentUserNickname,
|
||||
meshService = meshService,
|
||||
colorScheme = colorScheme,
|
||||
timeFormatter = timeFormatter,
|
||||
onNicknameClick = onNicknameClick,
|
||||
onMessageLongPress = onMessageLongPress,
|
||||
modifier = modifier,
|
||||
showSender = showSender,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates only the body content; sender, metadata, gestures, and spacing remain stable.
|
||||
*/
|
||||
@Composable
|
||||
private fun AnimatedMessageDisplay(
|
||||
message: BitchatMessage,
|
||||
currentUserNickname: String,
|
||||
meshService: MeshService,
|
||||
colorScheme: ColorScheme,
|
||||
timeFormatter: SimpleDateFormat,
|
||||
onNicknameClick: ((String) -> Unit)?,
|
||||
onMessageLongPress: ((BitchatMessage) -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
showSender: Boolean = true,
|
||||
) {
|
||||
var animatedContent by remember(message.id, message.content) {
|
||||
mutableStateOf(message.content)
|
||||
}
|
||||
|
||||
// Character-by-character animation state like the JavaScript version
|
||||
var characterStates by remember(message.id, message.content) {
|
||||
mutableStateOf(message.content.map { char ->
|
||||
if (char == ' ') CharacterAnimationState.FINAL else CharacterAnimationState.ENCRYPTED
|
||||
})
|
||||
}
|
||||
|
||||
LaunchedEffect(message.id, message.content) {
|
||||
if (message.content.isEmpty()) return@LaunchedEffect
|
||||
|
||||
val encryptedChars = "!@$%^&*()_+-=[]{}|;:,<>?".toCharArray()
|
||||
|
||||
// Start character animations with staggered delays (like JS version).
|
||||
message.content.forEachIndexed { index, targetChar ->
|
||||
if (targetChar != ' ') {
|
||||
launch {
|
||||
delay(index * 50L)
|
||||
|
||||
while (true) {
|
||||
while (characterStates.getOrNull(index) == CharacterAnimationState.ENCRYPTED) {
|
||||
val newContent = animatedContent.toCharArray()
|
||||
if (index < newContent.size) {
|
||||
newContent[index] = encryptedChars[Random.nextInt(encryptedChars.size)]
|
||||
animatedContent = String(newContent)
|
||||
}
|
||||
|
||||
delay(100L)
|
||||
|
||||
if (Random.nextFloat() < 0.1f) {
|
||||
val finalContent = animatedContent.toCharArray()
|
||||
if (index < finalContent.size) {
|
||||
finalContent[index] = targetChar
|
||||
animatedContent = String(finalContent)
|
||||
}
|
||||
|
||||
val finalStates = characterStates.toMutableList()
|
||||
finalStates[index] = CharacterAnimationState.FINAL
|
||||
characterStates = finalStates
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
delay(2000L)
|
||||
|
||||
val resetStates = characterStates.toMutableList()
|
||||
resetStates[index] = CharacterAnimationState.ENCRYPTED
|
||||
characterStates = resetStates
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextMessageLayout(
|
||||
message = message,
|
||||
currentUserNickname = currentUserNickname,
|
||||
meshService = meshService,
|
||||
colorScheme = colorScheme,
|
||||
timeFormatter = timeFormatter,
|
||||
onNicknameClick = onNicknameClick,
|
||||
onMessageLongPress = onMessageLongPress,
|
||||
modifier = modifier,
|
||||
showSender = showSender,
|
||||
bodyContent = animatedContent,
|
||||
)
|
||||
}
|
||||
@ -1,19 +1,25 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import com.bitchat.android.R
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Groups
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -37,6 +43,7 @@ import com.bitchat.android.favorites.FavoritesPersistenceService
|
||||
import com.bitchat.android.geohash.ChannelID
|
||||
import com.bitchat.android.identity.SecureIdentityStateManager
|
||||
import com.bitchat.android.ui.theme.BASE_FONT_SIZE
|
||||
import com.bitchat.android.ui.theme.BitchatMotion
|
||||
import com.bitchat.android.ui.theme.LocalBitchatPalette
|
||||
import com.bitchat.android.nostr.GeohashAliasRegistry
|
||||
import com.bitchat.android.nostr.GeohashConversationRegistry
|
||||
@ -116,8 +123,9 @@ fun MeshPeerListSheet(
|
||||
.padding(horizontal = SheetHorizontalPadding + 14.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
SheetHeaderBadge(icon = Icons.Filled.Groups)
|
||||
Text(
|
||||
SheetHeaderBadge(icon = Icons.Filled.Person)
|
||||
AnimatedCountLabel(
|
||||
count = peopleCount,
|
||||
text = stringResource(R.string.people_count_title, peopleCount),
|
||||
fontSize = 20.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
@ -212,13 +220,13 @@ fun MeshPeerListSheet(
|
||||
if (selectedLocationChannel !is ChannelID.Location) {
|
||||
IconButton(
|
||||
onClick = onShowVerification,
|
||||
modifier = Modifier.size(24.dp)
|
||||
modifier = Modifier.size(44.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.QrCode,
|
||||
contentDescription = stringResource(R.string.verify_title),
|
||||
tint = colorScheme.onSurface.copy(alpha = 0.8f),
|
||||
modifier = Modifier.size(18.dp)
|
||||
tint = LocalBitchatPalette.current.textSecondary,
|
||||
modifier = Modifier.size(22.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -231,6 +239,9 @@ fun MeshPeerListSheet(
|
||||
}
|
||||
}
|
||||
|
||||
/** Icon size for rows inside the sheets: one step down from the top bar. */
|
||||
private val PeerRowIconSize = 18.dp
|
||||
|
||||
@Composable
|
||||
private fun ChannelRow(
|
||||
channel: String,
|
||||
@ -580,28 +591,26 @@ private fun PeerItem(
|
||||
) {
|
||||
// Connection/status indicator
|
||||
if (hasUnreadDM) {
|
||||
// Show mail icon for unread DMs (iOS orange)
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Email,
|
||||
contentDescription = stringResource(R.string.cd_unread_message),
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = Color(0xFFFF9500) // iOS orange
|
||||
modifier = Modifier.size(PeerRowIconSize),
|
||||
tint = palette.accentOrange
|
||||
)
|
||||
} else if (showNostrGlobe) {
|
||||
// Purple globe to indicate Nostr availability
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Public,
|
||||
contentDescription = stringResource(R.string.cd_reachable_via_nostr),
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = Color(0xFF9C27B0) // Purple
|
||||
modifier = Modifier.size(PeerRowIconSize),
|
||||
tint = palette.accentPurple
|
||||
)
|
||||
} else if (!isDirect && isFavorite) {
|
||||
// Offline favorited user: show outlined circle icon
|
||||
// Offline favourite: routed glyph, dimmed to read as unavailable.
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Circle,
|
||||
contentDescription = stringResource(R.string.cd_offline_favorite),
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = Color.Gray
|
||||
modifier = Modifier.size(PeerRowIconSize),
|
||||
tint = palette.textTertiary
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
@ -615,8 +624,8 @@ private fun PeerItem(
|
||||
isDirect -> "Direct Bluetooth"
|
||||
else -> "Routed"
|
||||
},
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
modifier = Modifier.size(PeerRowIconSize),
|
||||
tint = palette.textSecondary
|
||||
)
|
||||
}
|
||||
|
||||
@ -649,8 +658,8 @@ private fun PeerItem(
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Wifi,
|
||||
contentDescription = "Direct Wi-Fi Aware",
|
||||
modifier = Modifier.size(13.dp),
|
||||
tint = colorScheme.onSurface.copy(alpha = 0.8f)
|
||||
modifier = Modifier.size(PeerRowIconSize),
|
||||
tint = palette.textSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -660,9 +669,9 @@ private fun PeerItem(
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Verified,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(14.dp),
|
||||
tint = Color(0xFF32D74B) // iOS Green
|
||||
contentDescription = stringResource(R.string.verify_title),
|
||||
modifier = Modifier.size(PeerRowIconSize),
|
||||
tint = palette.accentGreen
|
||||
)
|
||||
}
|
||||
|
||||
@ -673,13 +682,13 @@ private fun PeerItem(
|
||||
// Favorite star with proper filled/outlined states
|
||||
IconButton(
|
||||
onClick = onToggleFavorite,
|
||||
modifier = Modifier.size(32.dp)
|
||||
modifier = Modifier.size(44.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (isFavorite) Icons.Filled.Star else Icons.Outlined.Star,
|
||||
contentDescription = if (isFavorite) "Remove from favorites" else "Add to favorites",
|
||||
modifier = Modifier.size(16.dp),
|
||||
tint = if (isFavorite) Color(0xFFFFD700) else Color(0xFF4CAF50)
|
||||
modifier = Modifier.size(PeerRowIconSize),
|
||||
tint = if (isFavorite) palette.accentOrange else palette.textTertiary
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -696,25 +705,41 @@ private fun UnreadBadge(
|
||||
colorScheme: ColorScheme,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
if (count > 0) {
|
||||
val palette = LocalBitchatPalette.current
|
||||
// Scale/fade in and out so a badge appearing while the sheet is open is noticed, and one
|
||||
// clearing does not just blink away.
|
||||
AnimatedVisibility(
|
||||
visible = count > 0,
|
||||
enter = fadeIn(tween(BitchatMotion.STANDARD_MS)) +
|
||||
scaleIn(
|
||||
initialScale = 0.5f,
|
||||
animationSpec = tween(BitchatMotion.STANDARD_MS, easing = FastOutSlowInEasing)
|
||||
),
|
||||
exit = fadeOut(tween(BitchatMotion.QUICK_MS)) +
|
||||
scaleOut(
|
||||
targetScale = 0.5f,
|
||||
animationSpec = tween(BitchatMotion.QUICK_MS, easing = FastOutSlowInEasing)
|
||||
),
|
||||
modifier = modifier
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = Color(0xFFFFD700), // Yellow color
|
||||
color = palette.accentOrange,
|
||||
shape = RoundedCornerShape(10.dp)
|
||||
)
|
||||
.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||
.defaultMinSize(minWidth = 18.dp, minHeight = 18.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
AnimatedCountLabel(
|
||||
count = count,
|
||||
text = if (count > 99) "99+" else count.toString(),
|
||||
style = MaterialTheme.typography.labelSmall.copy(
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontFamily = FontFamily.Monospace
|
||||
),
|
||||
color = Color.Black // Black text on yellow background
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = Color.Black
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
@ -17,8 +19,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@ -393,24 +393,7 @@ fun MessageItem(
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this message should be animated during PoW mining
|
||||
val shouldAnimate = shouldAnimateMessage(message.id)
|
||||
|
||||
// If animation is needed, use the matrix animation component for content only
|
||||
if (shouldAnimate) {
|
||||
// Display message with matrix animation for content
|
||||
MessageWithMatrixAnimation(
|
||||
message = message,
|
||||
currentUserNickname = currentUserNickname,
|
||||
meshService = meshService,
|
||||
colorScheme = colorScheme,
|
||||
timeFormatter = timeFormatter,
|
||||
showSender = showSender,
|
||||
onNicknameClick = onNicknameClick,
|
||||
onMessageLongPress = onMessageLongPress,
|
||||
modifier = modifier
|
||||
)
|
||||
} else if (message.sender == "system") {
|
||||
if (message.sender == "system") {
|
||||
// Background narration: `// Tor started. Routing all chats…`
|
||||
val annotatedText = remember(message, palette) {
|
||||
formatSystemMessage(
|
||||
|
||||
@ -1,133 +0,0 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.bitchat.android.nostr.NostrProofOfWork
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bitchat.android.R
|
||||
import com.bitchat.android.nostr.PoWPreferenceManager
|
||||
|
||||
/**
|
||||
* Shows the current Proof of Work status and settings
|
||||
*/
|
||||
@Composable
|
||||
fun PoWStatusIndicator(
|
||||
modifier: Modifier = Modifier,
|
||||
style: PoWIndicatorStyle = PoWIndicatorStyle.COMPACT
|
||||
) {
|
||||
val powEnabled by PoWPreferenceManager.powEnabled.collectAsStateWithLifecycle()
|
||||
val powDifficulty by PoWPreferenceManager.powDifficulty.collectAsStateWithLifecycle()
|
||||
val isMining by PoWPreferenceManager.isMining.collectAsStateWithLifecycle()
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val isDark = colorScheme.background.red + colorScheme.background.green + colorScheme.background.blue < 1.5f
|
||||
|
||||
if (!powEnabled) return
|
||||
|
||||
when (style) {
|
||||
PoWIndicatorStyle.COMPACT -> {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// PoW icon with animation if mining
|
||||
if (isMining) {
|
||||
val rotation by rememberInfiniteTransition(label = "pow-rotation").animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 360f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(1000, easing = LinearEasing),
|
||||
repeatMode = RepeatMode.Restart
|
||||
),
|
||||
label = "pow-icon-rotation"
|
||||
)
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Security,
|
||||
contentDescription = stringResource(R.string.cd_mining_pow),
|
||||
tint = Color(0xFFFF9500), // Orange for mining
|
||||
modifier = Modifier
|
||||
.size(12.dp)
|
||||
.graphicsLayer { rotationZ = rotation }
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Security,
|
||||
contentDescription = stringResource(R.string.cd_pow_enabled),
|
||||
tint = if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D), // Green when ready
|
||||
modifier = Modifier.size(12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PoWIndicatorStyle.DETAILED -> {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
color = colorScheme.surfaceVariant.copy(alpha = 0.3f),
|
||||
shape = androidx.compose.foundation.shape.RoundedCornerShape(8.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// PoW icon
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Security,
|
||||
contentDescription = stringResource(R.string.cd_proof_of_work),
|
||||
tint = if (isMining) Color(0xFFFF9500) else {
|
||||
if (isDark) Color(0xFF32D74B) else Color(0xFF248A3D)
|
||||
},
|
||||
modifier = Modifier.size(14.dp)
|
||||
)
|
||||
|
||||
// Status text
|
||||
Text(
|
||||
text = if (isMining) {
|
||||
stringResource(R.string.pow_mining_ellipsis)
|
||||
} else {
|
||||
stringResource(R.string.pow_label_format, powDifficulty)
|
||||
},
|
||||
fontSize = 11.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = if (isMining) Color(0xFFFF9500) else {
|
||||
colorScheme.onSurface.copy(alpha = 0.7f)
|
||||
}
|
||||
)
|
||||
|
||||
// Time estimate
|
||||
if (!isMining && powDifficulty > 0) {
|
||||
Text(
|
||||
text = stringResource(R.string.pow_time_estimate, NostrProofOfWork.estimateMiningTime(powDifficulty)),
|
||||
fontSize = 9.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.5f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Style options for the PoW status indicator
|
||||
*/
|
||||
enum class PoWIndicatorStyle {
|
||||
COMPACT, // Small icon + difficulty number
|
||||
DETAILED // Icon + status text + time estimate
|
||||
}
|
||||
@ -1,5 +1,12 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Lock
|
||||
import androidx.compose.material.icons.filled.Verified
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material.icons.outlined.NoEncryption
|
||||
import androidx.compose.material.icons.outlined.Sync
|
||||
import androidx.compose.material.icons.outlined.Warning as OutlinedWarning
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@ -12,13 +19,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Lock
|
||||
import androidx.compose.material.icons.filled.Verified
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material.icons.outlined.NoEncryption
|
||||
import androidx.compose.material.icons.outlined.Sync
|
||||
import androidx.compose.material.icons.outlined.Warning as OutlinedWarning
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.QrCodeScanner
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@ -31,8 +33,6 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.QrCodeScanner
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Mic
|
||||
import android.Manifest
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Mic
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -132,7 +132,7 @@ fun VoiceRecordButton(
|
||||
imageVector = Icons.Filled.Mic,
|
||||
contentDescription = stringResource(com.bitchat.android.R.string.cd_record_voice),
|
||||
tint = Color.Black,
|
||||
modifier = Modifier.size(20.dp)
|
||||
modifier = Modifier.size(22.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,13 @@
|
||||
package com.bitchat.android.ui.debug
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bluetooth
|
||||
import androidx.compose.material.icons.filled.Wifi
|
||||
import androidx.compose.material.icons.filled.WifiTethering
|
||||
import androidx.compose.material.icons.filled.BugReport
|
||||
import androidx.compose.material.icons.filled.Devices
|
||||
import androidx.compose.material.icons.filled.PowerSettingsNew
|
||||
import androidx.compose.material.icons.filled.SettingsEthernet
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
@ -9,14 +17,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bluetooth
|
||||
import androidx.compose.material.icons.filled.Wifi
|
||||
import androidx.compose.material.icons.filled.WifiTethering
|
||||
import androidx.compose.material.icons.filled.BugReport
|
||||
import androidx.compose.material.icons.filled.Devices
|
||||
import androidx.compose.material.icons.filled.PowerSettingsNew
|
||||
import androidx.compose.material.icons.filled.SettingsEthernet
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
package com.bitchat.android.ui.debug
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Wifi
|
||||
import androidx.compose.material.icons.outlined.Bluetooth
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.gestures.detectDragGestures
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Wifi
|
||||
import androidx.compose.material.icons.outlined.Bluetooth
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
package com.bitchat.android.ui.media
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.bitchat.android.ui.media
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Description
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@ -12,8 +14,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Description
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
package com.bitchat.android.ui.media
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Attachment
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Attachment
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.bitchat.android.ui.media
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Description
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
@ -11,8 +13,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Description
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
package com.bitchat.android.ui.media
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import android.content.ContentValues
|
||||
import android.os.Build
|
||||
import android.provider.MediaStore
|
||||
@ -10,9 +13,6 @@ import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
package com.bitchat.android.ui.media
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
package com.bitchat.android.ui.media
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Camera
|
||||
import androidx.compose.material.icons.filled.Photo
|
||||
import androidx.compose.material.icons.filled.PhotoCamera
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
@ -10,10 +14,6 @@ import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Camera
|
||||
import androidx.compose.material.icons.filled.Photo
|
||||
import androidx.compose.material.icons.filled.PhotoCamera
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -91,8 +91,8 @@ fun ImagePickerButton(
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
// 40.dp to match the header's tap targets and the composer's send button.
|
||||
.size(40.dp)
|
||||
// Matches the header's tap targets and the composer's send button.
|
||||
.size(44.dp)
|
||||
.clip(CircleShape)
|
||||
.combinedClickable(
|
||||
onClick = { imagePicker.launch("image/*") },
|
||||
@ -110,7 +110,7 @@ fun ImagePickerButton(
|
||||
imageVector = Icons.Filled.PhotoCamera,
|
||||
contentDescription = stringResource(com.bitchat.android.R.string.pick_image),
|
||||
tint = LocalBitchatPalette.current.textSecondary,
|
||||
modifier = Modifier.size(20.dp)
|
||||
modifier = Modifier.size(22.dp)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
package com.bitchat.android.ui.media
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Description
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@ -9,9 +12,6 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Description
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
package com.bitchat.android.ui.media
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Pause
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import android.media.MediaPlayer
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Pause
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material3.FilledTonalIconButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
||||
@ -22,7 +22,7 @@ data class BitchatPalette(
|
||||
val isDark: Boolean,
|
||||
|
||||
// MARK: - Surfaces
|
||||
/** Screen, header and composer background. Slightly green-tinted near-black. */
|
||||
/** Screen, header and composer background. True black in dark mode. */
|
||||
val background: Color,
|
||||
/** Sheets, cards and list rows: one step above [background]. */
|
||||
val surface: Color,
|
||||
@ -56,7 +56,10 @@ data class BitchatPalette(
|
||||
|
||||
val DarkBitchatPalette = BitchatPalette(
|
||||
isDark = true,
|
||||
background = Color(0xFF070D07),
|
||||
// True black. OLED panels switch these pixels off entirely, which is both the terminal
|
||||
// aesthetic the app is going for and a real battery win on a screen that is mostly
|
||||
// background.
|
||||
background = Color(0xFF000000),
|
||||
surface = Color(0xFF0E150E),
|
||||
surfaceVariant = Color(0xFF182118),
|
||||
outline = Color(0xFF2A3A2A),
|
||||
@ -73,7 +76,7 @@ val DarkBitchatPalette = BitchatPalette(
|
||||
|
||||
val LightBitchatPalette = BitchatPalette(
|
||||
isDark = false,
|
||||
background = Color(0xFFFBFDFB),
|
||||
background = Color(0xFFFFFFFF),
|
||||
surface = Color(0xFFF2F6F2),
|
||||
surfaceVariant = Color(0xFFE7EDE7),
|
||||
outline = Color(0xFFCBD6CB),
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">مغادرة</string>
|
||||
<string name="cd_nostr_reachable">يمكن الوصول عبر Nostr</string>
|
||||
<string name="cd_unread_private_messages">رسائل خاصة غير مقروءة</string>
|
||||
<string name="cd_toggle_bookmark">تبديل الإشارة المرجعية</string>
|
||||
<string name="cd_teleported">تم الانتقال</string>
|
||||
<string name="cd_tor_status">حالة Tor</string>
|
||||
<string name="cd_connected_peers">الأقران المتصلون</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">ছেড়ে দিন</string>
|
||||
<string name="cd_nostr_reachable">Nostr এর মাধ্যমে পৌঁছানো যায়</string>
|
||||
<string name="cd_unread_private_messages">অপঠিত ব্যক্তিগত বার্তা</string>
|
||||
<string name="cd_toggle_bookmark">বুকমার্ক টগল করুন</string>
|
||||
<string name="cd_teleported">টেলিপোর্ট করা হয়েছে</string>
|
||||
<string name="cd_tor_status">Tor অবস্থা</string>
|
||||
<string name="cd_connected_peers">সংযুক্ত পিয়ার</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">verlassen</string>
|
||||
<string name="cd_nostr_reachable">Über Nostr erreichbar</string>
|
||||
<string name="cd_unread_private_messages">Ungelesene private Nachrichten</string>
|
||||
<string name="cd_toggle_bookmark">Lesezeichen umschalten</string>
|
||||
<string name="cd_teleported">Teleportiert</string>
|
||||
<string name="cd_tor_status">Tor‑Status</string>
|
||||
<string name="cd_connected_peers">Verbundenen Peers</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">salir</string>
|
||||
<string name="cd_nostr_reachable">Accesible vía Nostr</string>
|
||||
<string name="cd_unread_private_messages">Mensajes privados sin leer</string>
|
||||
<string name="cd_toggle_bookmark">Alternar marcador</string>
|
||||
<string name="cd_teleported">Teletransportado</string>
|
||||
<string name="cd_tor_status">Estado de Tor</string>
|
||||
<string name="cd_connected_peers">Pares conectados</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">خروج</string>
|
||||
<string name="cd_nostr_reachable">قابل دسترس از طریق Nostr</string>
|
||||
<string name="cd_unread_private_messages">پیامهای خصوصی خواندهنشده</string>
|
||||
<string name="cd_toggle_bookmark">تغییر وضعیت نشانک</string>
|
||||
<string name="cd_teleported">انتقال مکانی</string>
|
||||
<string name="cd_tor_status">وضعیت Tor</string>
|
||||
<string name="cd_connected_peers">همتایان متصل</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">umalis</string>
|
||||
<string name="cd_nostr_reachable">Maabot sa Nostr</string>
|
||||
<string name="cd_unread_private_messages">Hindi nabasang pribadong mensahe</string>
|
||||
<string name="cd_toggle_bookmark">I‑toggle ang bookmark</string>
|
||||
<string name="cd_teleported">Naiteleport</string>
|
||||
<string name="cd_tor_status">Katayuan ng Tor</string>
|
||||
<string name="cd_connected_peers">Nakakonektang peers</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">quitter</string>
|
||||
<string name="cd_nostr_reachable">Joignable via Nostr</string>
|
||||
<string name="cd_unread_private_messages">Messages privés non lus</string>
|
||||
<string name="cd_toggle_bookmark">Basculer le signet</string>
|
||||
<string name="cd_teleported">Téléporté</string>
|
||||
<string name="cd_tor_status">Statut Tor</string>
|
||||
<string name="cd_connected_peers">Pairs connectés</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">छोड़ें</string>
|
||||
<string name="cd_nostr_reachable">Nostr के माध्यम से उपलब्ध</string>
|
||||
<string name="cd_unread_private_messages">अपठित निजी संदेश</string>
|
||||
<string name="cd_toggle_bookmark">बुकमार्क टॉगल करें</string>
|
||||
<string name="cd_teleported">टेलीपोर्ट हुआ</string>
|
||||
<string name="cd_tor_status">Tor स्थिति</string>
|
||||
<string name="cd_connected_peers">कनेक्टेड पीयर्स</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">keluar</string>
|
||||
<string name="cd_nostr_reachable">Dapat dijangkau melalui Nostr</string>
|
||||
<string name="cd_unread_private_messages">Pesan pribadi yang belum dibaca</string>
|
||||
<string name="cd_toggle_bookmark">Alihkan bookmark</string>
|
||||
<string name="cd_teleported">Diteleportasi</string>
|
||||
<string name="cd_tor_status">Status Tor</string>
|
||||
<string name="cd_connected_peers">Peer yang terhubung</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">esci</string>
|
||||
<string name="cd_nostr_reachable">Raggiungibile via Nostr</string>
|
||||
<string name="cd_unread_private_messages">Messaggi privati non letti</string>
|
||||
<string name="cd_toggle_bookmark">Attiva/disattiva segnalibro</string>
|
||||
<string name="cd_teleported">Teletrasportato</string>
|
||||
<string name="cd_tor_status">Stato Tor</string>
|
||||
<string name="cd_connected_peers">Peer connessi</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">退出</string>
|
||||
<string name="cd_nostr_reachable">Nostr 経由で到達可能</string>
|
||||
<string name="cd_unread_private_messages">未読のプライベートメッセージ</string>
|
||||
<string name="cd_toggle_bookmark">ブックマークを切り替え</string>
|
||||
<string name="cd_teleported">テレポート済み</string>
|
||||
<string name="cd_tor_status">Tor ステータス</string>
|
||||
<string name="cd_connected_peers">接続中のピア</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">გასვლა</string>
|
||||
<string name="cd_nostr_reachable">მისაწვდომია Nostr-ით</string>
|
||||
<string name="cd_unread_private_messages">წაუკითხავი პირადი შეტყობინებები</string>
|
||||
<string name="cd_toggle_bookmark">სანიშნეს გადართვა</string>
|
||||
<string name="cd_teleported">ტელეპორტირებული</string>
|
||||
<string name="cd_tor_status">Tor-ის სტატუსი</string>
|
||||
<string name="cd_connected_peers">დაკავშირებული პირები</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">나가기</string>
|
||||
<string name="cd_nostr_reachable">Nostr로 연결 가능</string>
|
||||
<string name="cd_unread_private_messages">읽지 않은 개인 메시지</string>
|
||||
<string name="cd_toggle_bookmark">북마크 전환</string>
|
||||
<string name="cd_teleported">텔레포트됨</string>
|
||||
<string name="cd_tor_status">Tor 상태</string>
|
||||
<string name="cd_connected_peers">연결된 피어</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">hiala</string>
|
||||
<string name="cd_nostr_reachable">Azo tratrarina amin\'ny alalan\'ny Nostr</string>
|
||||
<string name="cd_unread_private_messages">Hafatra manokana tsy voavaky</string>
|
||||
<string name="cd_toggle_bookmark">Hanova bookmark</string>
|
||||
<string name="cd_teleported">Nafindra toerana</string>
|
||||
<string name="cd_tor_status">Toe-javatra Tor</string>
|
||||
<string name="cd_connected_peers">Mpiara-miasa mifandray</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">छोड्नुहोस्</string>
|
||||
<string name="cd_nostr_reachable">Nostr मार्फत पुग्न सकिने</string>
|
||||
<string name="cd_unread_private_messages">नपढिएका निजी सन्देश</string>
|
||||
<string name="cd_toggle_bookmark">बुकमार्क टगल</string>
|
||||
<string name="cd_teleported">टेलिपोर्ट भयो</string>
|
||||
<string name="cd_tor_status">Tor स्थिति</string>
|
||||
<string name="cd_connected_peers">जडान भएका साथीहरू</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">verlaten</string>
|
||||
<string name="cd_nostr_reachable">Bereikbaar via Nostr</string>
|
||||
<string name="cd_unread_private_messages">Ongelezen privéberichten</string>
|
||||
<string name="cd_toggle_bookmark">Bladwijzer omschakelen</string>
|
||||
<string name="cd_teleported">Geteleporteerd</string>
|
||||
<string name="cd_tor_status">Tor-status</string>
|
||||
<string name="cd_connected_peers">Verbonden peers</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">باہر آؤ</string>
|
||||
<string name="cd_nostr_reachable">Nostr راہین پہنچ ممکن اے</string>
|
||||
<string name="cd_unread_private_messages">انہ پڑھیا ذاتی پیغام</string>
|
||||
<string name="cd_toggle_bookmark">بُک مارک ٹوگل کرو</string>
|
||||
<string name="cd_teleported">ٹیلپورٹ کیتا گیا</string>
|
||||
<string name="cd_tor_status">Tor حالت</string>
|
||||
<string name="cd_connected_peers">جُڑے پیئر</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">sair</string>
|
||||
<string name="cd_nostr_reachable">Acessível via Nostr</string>
|
||||
<string name="cd_unread_private_messages">Mensagens privadas não lidas</string>
|
||||
<string name="cd_toggle_bookmark">Alternar favorito</string>
|
||||
<string name="cd_teleported">Teletransportado</string>
|
||||
<string name="cd_tor_status">Status do Tor</string>
|
||||
<string name="cd_connected_peers">Peers conectados</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">sair</string>
|
||||
<string name="cd_nostr_reachable">Alcançável via Nostr</string>
|
||||
<string name="cd_unread_private_messages">Mensagens privadas não lidas</string>
|
||||
<string name="cd_toggle_bookmark">Alternar marcador</string>
|
||||
<string name="cd_teleported">Teletransportado</string>
|
||||
<string name="cd_tor_status">Estado do Tor</string>
|
||||
<string name="cd_connected_peers">Pares ligados</string>
|
||||
|
||||
@ -67,7 +67,6 @@
|
||||
<string name="chat_leave">выйти</string>
|
||||
<string name="cd_nostr_reachable">Доступен через Nostr</string>
|
||||
<string name="cd_unread_private_messages">Непрочитанные личные</string>
|
||||
<string name="cd_toggle_bookmark">Переключить закладку</string>
|
||||
<string name="cd_teleported">Телепортировано</string>
|
||||
<string name="cd_tor_status">Статус Tor</string>
|
||||
<string name="cd_connected_peers">Подключённые узлы</string>
|
||||
|
||||
@ -67,7 +67,6 @@
|
||||
<string name="chat_leave">lämna</string>
|
||||
<string name="cd_nostr_reachable">Nås via Nostr</string>
|
||||
<string name="cd_unread_private_messages">Olästa privata meddelanden</string>
|
||||
<string name="cd_toggle_bookmark">Växla bokmärke</string>
|
||||
<string name="cd_teleported">Teleporterad</string>
|
||||
<string name="cd_tor_status">Tor‑status</string>
|
||||
<string name="cd_connected_peers">Anslutna noder</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">ออก</string>
|
||||
<string name="cd_nostr_reachable">ติดต่อได้ผ่าน Nostr</string>
|
||||
<string name="cd_unread_private_messages">ข้อความส่วนตัวยังไม่ได้อ่าน</string>
|
||||
<string name="cd_toggle_bookmark">สลับที่คั่นหน้า</string>
|
||||
<string name="cd_teleported">เทเลพอร์ตแล้ว</string>
|
||||
<string name="cd_tor_status">สถานะ Tor</string>
|
||||
<string name="cd_connected_peers">เพียร์ที่เชื่อมต่อ</string>
|
||||
|
||||
@ -67,7 +67,6 @@
|
||||
<string name="chat_leave">ayrıl</string>
|
||||
<string name="cd_nostr_reachable">Nostr üzerinden ulaşılabilir</string>
|
||||
<string name="cd_unread_private_messages">Okunmamış özel mesaj</string>
|
||||
<string name="cd_toggle_bookmark">Yer imini değiştir</string>
|
||||
<string name="cd_teleported">Teleport edildi</string>
|
||||
<string name="cd_tor_status">Tor durumu</string>
|
||||
<string name="cd_connected_peers">Bağlı eşler</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">چھوڑیں</string>
|
||||
<string name="cd_nostr_reachable">Nostr کے ذریعے رسائی</string>
|
||||
<string name="cd_unread_private_messages">غیر پڑھے ہوئے نجی پیغامات</string>
|
||||
<string name="cd_toggle_bookmark">بک مارک تبدیل کریں</string>
|
||||
<string name="cd_teleported">ٹیلی پورٹ</string>
|
||||
<string name="cd_tor_status">Tor کی کیفیت</string>
|
||||
<string name="cd_connected_peers">منسلک پیئرز</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">rời khỏi</string>
|
||||
<string name="cd_nostr_reachable">Có thể tiếp cận qua Nostr</string>
|
||||
<string name="cd_unread_private_messages">Tin nhắn riêng chưa đọc</string>
|
||||
<string name="cd_toggle_bookmark">Bật/tắt bookmark</string>
|
||||
<string name="cd_teleported">Đã dịch chuyển</string>
|
||||
<string name="cd_tor_status">Trạng thái Tor</string>
|
||||
<string name="cd_connected_peers">Peers đã kết nối</string>
|
||||
|
||||
@ -75,7 +75,6 @@
|
||||
<string name="chat_leave">离开</string>
|
||||
<string name="cd_nostr_reachable">可通过 Nostr 联系</string>
|
||||
<string name="cd_unread_private_messages">未读私信</string>
|
||||
<string name="cd_toggle_bookmark">切换书签</string>
|
||||
<string name="cd_teleported">已传送</string>
|
||||
<string name="cd_tor_status">Tor 状态</string>
|
||||
<string name="cd_connected_peers">已连接节点</string>
|
||||
|
||||
@ -76,7 +76,6 @@
|
||||
<string name="chat_leave">leave</string>
|
||||
<string name="cd_nostr_reachable">Nostr reachable</string>
|
||||
<string name="cd_unread_private_messages">Unread private messages</string>
|
||||
<string name="cd_toggle_bookmark">Toggle bookmark</string>
|
||||
<string name="cd_location_notes">Location notes</string>
|
||||
<string name="cd_teleported">Teleported</string>
|
||||
<string name="cd_tor_status">Tor status</string>
|
||||
@ -111,8 +110,8 @@
|
||||
<string name="about_tagline">The sidegroup chat</string>
|
||||
|
||||
<!-- About sheet: tabs -->
|
||||
<string name="about_tab_how_to_use">How to use</string>
|
||||
<string name="about_tab_basic_info">Basic info</string>
|
||||
<string name="about_tab_info">Info</string>
|
||||
<string name="about_tab_settings">Settings</string>
|
||||
|
||||
<!-- About sheet: how-to-use instructions -->
|
||||
<string name="about_how_to_use_heading">How To Use</string>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user